C++ 程式設計 - 第 5 章
RAII 技術通常用於控制多執行緒應用程式中的執行緒鎖。另一個典型的 RAII 示例是檔案操作,例如 C++ 標準庫的檔案流。輸入檔案流在物件的建構函式中開啟,並在物件銷燬時關閉。由於 C++ 允許在 堆疊 上分配物件,因此可以使用 C++ 的作用域機制來控制檔案訪問。
使用 RAII,我們可以使用類解構函式來保證清理,類似於其他語言中的 finally 關鍵字。這樣做可以自動化任務,從而避免錯誤,但也提供了不使用它的自由。
RAII 也用於(如以下示例所示)確保異常安全。RAII 使得在不使用大量 try/catch 塊的情況下避免資源洩漏成為可能,並且在軟體行業中被廣泛使用。
動態分配記憶體的擁有權(使用new)可以使用 RAII 進行控制。為此,C++ 標準庫定義了 auto ptr。此外,共享物件的生存期可以透過具有共享所有權語義的智慧指標進行管理,例如 C++ 中由 Boost 庫 定義的 boost::shared_ptr 或基於策略的 Loki::SmartPtr 來自 Loki 庫。
以下 RAII 類是對 C 標準庫檔案系統呼叫的輕量級包裝器。
#include <cstdio>
// exceptions
class file_error { } ;
class open_error : public file_error { } ;
class close_error : public file_error { } ;
class write_error : public file_error { } ;
class file
{
public:
file( const char* filename )
:
m_file_handle(std::fopen(filename, "w+"))
{
if( m_file_handle == NULL )
{
throw open_error() ;
}
}
~file()
{
std::fclose(m_file_handle) ;
}
void write( const char* str )
{
if( std::fputs(str, m_file_handle) == EOF )
{
throw write_error() ;
}
}
void write( const char* buffer, std::size_t num_chars )
{
if( num_chars != 0
&&
std::fwrite(buffer, num_chars, 1, m_file_handle) == 0 )
{
throw write_error() ;
}
}
private:
std::FILE* m_file_handle ;
// copy and assignment not implemented; prevent their use by
// declaring private.
file( const file & ) ;
file & operator=( const file & ) ;
} ;
此 RAII 類可以使用如下方式
void example_with_RAII()
{
// open file (acquire resource)
file logfile("logfile.txt") ;
logfile.write("hello logfile!") ;
// continue writing to logfile.txt ...
// logfile.txt will automatically be closed because logfile's
// destructor is always called when example_with_RAII() returns or
// throws an exception.
}
如果不使用 RAII,每個使用輸出日誌的函式都必須顯式地管理檔案。例如,不使用 RAII 的等效實現如下所示
void example_without_RAII()
{
// open file
std::FILE* file_handle = std::fopen("logfile.txt", "w+") ;
if( file_handle == NULL )
{
throw open_error() ;
}
try
{
if( std::fputs("hello logfile!", file_handle) == EOF )
{
throw write_error() ;
}
// continue writing to logfile.txt ... do not return
// prematurely, as cleanup happens at the end of this function
}
catch(...)
{
// manually close logfile.txt
std::fclose(file_handle) ;
// re-throw the exception we just caught
throw ;
}
// manually close logfile.txt
std::fclose(file_handle) ;
}
如果 fopen() 和 fclose() 可能會丟擲異常,file 和 example_without_RAII() 的實現將變得更加複雜;然而,example_with_RAII() 將不受影響。
RAII 習語的本質是類 file 封裝了對任何有限資源(如 FILE* 檔案控制代碼)的管理。它保證在函式退出時資源將被正確處理。此外,file 例項保證有可用的有效日誌檔案(如果檔案無法開啟,則丟擲異常)。
在存在異常的情況下,還有一個很大的問題:在example_without_RAII()中,如果分配了多個資源,但在其分配之間丟擲異常,則沒有通用方法來知道在最終catch塊中需要釋放哪些資源,釋放未分配的資源通常是一件壞事。RAII 可以解決這個問題;自動變數以與它們構造相反的順序析構,並且僅在物件完全構造(建構函式內部沒有丟擲異常)時才會析構物件。因此,example_without_RAII()永遠不能像example_with_RAII()一樣安全,除非為每種情況進行特殊編碼,例如檢查無效的預設值或巢狀 try-catch 塊。實際上,需要注意的是,example_without_RAII()在本篇文章的先前版本中包含資源錯誤。
這使example_with_RAII()無需像其他情況一樣顯式管理資源。當多個函式使用file時,這可以簡化並減少程式碼的整體大小,並有助於確保程式正確性。
example_without_RAII()類似於在非 RAII 語言(例如 Java)中用於資源管理的習慣用法。雖然 Java 的try-finally塊允許正確釋放資源,但負擔仍然落在程式設計師身上,以確保正確行為,因為每個使用file的函式都可能顯式地要求使用try-finally塊銷燬日誌檔案。
垃圾回收是一種自動記憶體管理形式。垃圾收集器試圖回收垃圾,或者說是由永遠不會被應用程式再次訪問或修改的物件使用的記憶體。
跟蹤式垃圾收集器需要一些隱式的執行時開銷,這可能超出程式設計師的控制,有時會導致效能問題。例如,常用的 Stop-The-World 垃圾收集器會在任意時間暫停程式執行,這可能使垃圾收集語言不適合某些嵌入式系統、高效能伺服器軟體和具有即時需求的應用程式。
一個更基本的問題是,垃圾收集器違反了區域性性原理,因為它們故意去尋找最近沒有被訪問的記憶體位。現代計算機體系結構的效能越來越依賴於快取,而快取的有效性依賴於區域性性原理的假設。一些垃圾收集方法比其他方法具有更好的區域性性。分代式垃圾收集器相對快取友好,複製式收集器會自動碎片整理記憶體,從而幫助將相關資料放在一起。儘管如此,垃圾收集週期的時機不當會對某些計算產生嚴重的影響,出於這個原因,許多執行時系統提供了允許程式臨時暫停、延遲或啟用垃圾收集週期的機制。
儘管存在這些問題,但對於許多實際目的來說,在現代垃圾收集語言中實現的分配/釋放密集型演算法實際上比使用顯式記憶體管理的等效演算法快(至少沒有專家程式設計師進行英雄式最佳化)。造成這種情況的主要原因是垃圾收集器允許執行時系統以一種可能有利的方式攤銷分配和釋放操作。例如,考慮以下 C++ 程式
#include <iostream>
class A {
int x;
public:
A() { x = 0; ++x; }
};
int main() {
for (int i = 0; i < 1000000000; ++i) {
A *a = new A();
delete a;
}
std::cout << "DING!" << std::endl;
}
提供此功能的最廣泛使用的庫之一是Hans Boehm 的保守式 GC。Oilpan 是一個針對 C++ 的垃圾收集器,最初是為 Blink 設計的。正如我們之前所見,C++ 還支援一個稱為RAII(資源獲取即初始化)的強大習慣用法,它可以用來安全地自動管理資源,包括記憶體。
軟體設計模式是幫助構建系統設計的抽象。雖然這不是新事物,因為這個概念已經由克里斯托弗·亞歷山大在其建築理論中描述過,但由於設計模式:可複用面向物件軟體的要素一書於 1994 年 10 月由Erich Gamma、Richard Helm、Ralph Johnson 和John Vlissides出版,它才在程式設計中得到了一些關注,他們被稱為四人幫(GoF),該書識別並描述了 23 種經典的軟體設計模式。
設計模式既不是靜態解決方案,也不是演算法。模式是一種描述和命名可重複解決方案或解決常見設計問題的方法,即解決通用問題的通用方法(模式的通用性或特殊性取決於目標的限制程度)。模式可以自行出現,也可以透過設計出現。這就是設計模式作為實現的抽象和設計階段的幫助的有用之處。有了這個概念,可以提供一種更簡單的方法來促進對設計選擇的溝通,作為規範化技術,以便每個人都可以分享設計概念。
根據它們解決的設計問題,設計模式可以分為不同的類別,其中主要類別是
模式通常出現在面向物件的程式語言(如 C++ 或 Java)中。它們可以被視為解決在許多不同情況或應用程式中出現的問題的模板。它不是程式碼重用,因為它通常不指定程式碼,但可以很容易地從設計模式建立程式碼。面向物件的設計模式通常展示類或物件之間的關係和互動,而不指定最終涉及的應用程式類或物件。
每個設計模式都包含以下部分
- 問題/需求
- 為了使用設計模式,我們需要進行一個迷你分析設計,可以將其編碼以測試解決方案。本節說明我們要解決問題的需求。這通常是一個常見問題,將在多個應用程式中出現。
- 力量
- 本節說明了技術邊界,這些邊界有助於並指導解決方案的建立。
- 解決方案
- 本節描述瞭如何編寫程式碼來解決上述問題。這是設計模式的設計部分。它可能包含類圖、序列圖,以及描述如何編碼解決方案所需的任何其他內容。
設計模式可以被視為對解決特定設計問題的普遍認可的最佳實踐的標準化。應該將它們理解為在應用程式中實施良好設計模式的一種方式。這樣做將減少使用效率低下和模糊的解決方案。使用設計模式可以加快您的設計速度,並幫助您將設計傳達給其他程式設計師。
在軟體工程中,建立型設計模式是處理物件建立機制的設計模式,試圖以適合情況的方式建立物件。物件的建立基本形式可能會導致設計問題或增加設計的複雜性。建立型設計模式透過某種方式控制這種物件建立來解決這個問題。
在本節中,我們假設讀者已經對函式、全域性變數、棧與堆、類、指標以及之前介紹過的靜態成員函式足夠熟悉。
正如我們將看到的那樣,存在幾種建立型設計模式,它們都將處理特定的實現任務,這將為程式碼庫建立更高層次的抽象,我們現在將介紹每種模式。
建造者建立型模式用於將複雜物件的構造與其表示分離,以便相同的構造過程可以建立不同的物件表示。
- 問題
- 我們想要構造一個複雜的物件,但是我們不想擁有一個複雜的建構函式成員,或者一個需要許多引數的建構函式成員。
- 解決方案
- 定義一箇中間物件,其成員函式在物件對客戶端可用之前定義所需的逐部分物件。建造者模式使我們能夠將物件的構造推遲到所有建立選項都已指定之後。
#include <string>
#include <iostream>
#include <memory>
using namespace std;
// "Product"
class Pizza
{
public:
void setDough(const string& dough)
{
m_dough = dough;
}
void setSauce(const string& sauce)
{
m_sauce = sauce;
}
void setTopping(const string& topping)
{
m_topping = topping;
}
void open() const
{
cout << "Pizza with " << m_dough << " dough, " << m_sauce << " sauce and "
<< m_topping << " topping. Mmm." << endl;
}
private:
string m_dough;
string m_sauce;
string m_topping;
};
// "Abstract Builder"
class PizzaBuilder
{
public:
virtual ~PizzaBuilder() {};
Pizza* getPizza()
{
return m_pizza.get();
}
void createNewPizzaProduct()
{
m_pizza = make_unique<Pizza>();
}
virtual void buildDough() = 0;
virtual void buildSauce() = 0;
virtual void buildTopping() = 0;
protected:
unique_ptr<Pizza> m_pizza;
};
//----------------------------------------------------------------
class HawaiianPizzaBuilder : public PizzaBuilder
{
public:
virtual ~HawaiianPizzaBuilder() {};
virtual void buildDough()
{
m_pizza->setDough("cross");
}
virtual void buildSauce()
{
m_pizza->setSauce("mild");
}
virtual void buildTopping()
{
m_pizza->setTopping("ham+pineapple");
}
};
class SpicyPizzaBuilder : public PizzaBuilder
{
public:
virtual ~SpicyPizzaBuilder() {};
virtual void buildDough()
{
m_pizza->setDough("pan baked");
}
virtual void buildSauce()
{
m_pizza->setSauce("hot");
}
virtual void buildTopping()
{
m_pizza->setTopping("pepperoni+salami");
}
};
//----------------------------------------------------------------
class Cook
{
public:
void openPizza()
{
m_pizzaBuilder->getPizza()->open();
}
void makePizza(PizzaBuilder* pb)
{
m_pizzaBuilder = pb;
m_pizzaBuilder->createNewPizzaProduct();
m_pizzaBuilder->buildDough();
m_pizzaBuilder->buildSauce();
m_pizzaBuilder->buildTopping();
}
private:
PizzaBuilder* m_pizzaBuilder;
};
int main()
{
Cook cook;
HawaiianPizzaBuilder hawaiianPizzaBuilder;
SpicyPizzaBuilder spicyPizzaBuilder;
cook.makePizza(&hawaiianPizzaBuilder);
cook.openPizza();
cook.makePizza(&spicyPizzaBuilder);
cook.openPizza();
}
您也可以使用最新的 c++17 標準版本
#include <iostream>
#include <memory>
class Pizza{
public:
void setDough(const std::string& dough){
m_dough = dough;
}
void setSauce(const std::string& sauce){
m_sauce = sauce;
}
void setTopping(const std::string& topping){
m_topping = topping;
}
void open() const {
std::cout<<"The Pizza have "<<
m_dough<<" dough, "<<
m_sauce<<" sauce, "<<
m_topping<<" topping."<<
std::endl;
}
private:
std::string m_dough;
std::string m_sauce;
std::string m_topping;
};
class PizzaBuilder{
public:
virtual ~PizzaBuilder() = default;
void createNewPizza(){
m_pizza = std::make_unique<Pizza>();
}
Pizza* getPizza() {
return m_pizza.release();
}
virtual void buildDough() = 0;
virtual void buildSauce() = 0;
virtual void buildTopping() = 0;
protected:
std::unique_ptr<Pizza> m_pizza;
};
class HawaiianPizzaBuilder:public PizzaBuilder{
public:
~HawaiianPizzaBuilder() override = default;
void buildDough() override {
m_pizza->setDough("Hawaiian dough");
}
void buildSauce() override {
m_pizza->setSauce("Hawaiian sauce");
}
void buildTopping() override {
m_pizza->setTopping("Hawaiian topping");
}
};
class SpicyPizzaBuilder:public PizzaBuilder{
public:
~SpicyPizzaBuilder() override = default;
void buildDough() override {
m_pizza->setDough("Spicy dough");
}
void buildSauce() override {
m_pizza->setSauce("Spicy sauce");
}
void buildTopping() override {
m_pizza->setTopping("Spicy topping");
}
};
class Cook{
public:
void openPizza() const {
m_pizzaBuilder->getPizza()->open();
}
void createPizza(PizzaBuilder* pizzaBuilder){
m_pizzaBuilder = pizzaBuilder;
m_pizzaBuilder->createNewPizza();
m_pizzaBuilder->buildDough();
m_pizzaBuilder->buildSauce();
m_pizzaBuilder->buildTopping();
}
private:
PizzaBuilder* m_pizzaBuilder;
};
int main(){
Cook cook{};
HawaiianPizzaBuilder hawaiianPizzaBuilder;
cook.createPizza(&hawaiianPizzaBuilder);
cook.openPizza();
SpicyPizzaBuilder spicyPizzaBuilder;
cook.createPizza(&spicyPizzaBuilder);
cook.openPizza();
}
//console output
//The Pizza have Hawaiian dough dough, Hawaiian sauce sauce, Hawaiian topping topping.
//The Pizza have Spicy dough dough, Spicy sauce sauce, Spicy topping topping.
定義:一個實用程式類,它從派生類族中建立一個類的例項。
定義:一個實用程式類,它建立幾個類族的例項。它也可以返回特定組的工廠。
工廠設計模式在需要建立許多不同型別的物件的情況下很有用,所有這些物件都源自一個通用的基類。工廠方法定義了一個用於建立物件的方法,子類可以覆蓋該方法來指定將建立的派生型別。因此,在執行時,可以將工廠方法傳遞一個所需物件的描述(例如,從使用者輸入中讀取的字串),並返回指向該物件的新例項的基類指標。當對基類使用精心設計的介面時,該模式效果最佳,因此無需轉換返回的物件。
- 問題
- 我們希望在執行時根據一些配置或應用程式引數來決定要建立哪個物件。在編寫程式碼時,我們不知道應該例項化哪個類。
- 解決方案
- 定義一個用於建立物件的介面,但讓子類決定要例項化哪個類。工廠方法允許類將例項化推遲到子類。
在以下示例中,工廠方法用於在執行時建立筆記型電腦或桌上型電腦物件。
讓我們從定義Computer開始,它是一個抽象基類(介面)及其派生類:Laptop和Desktop。
class Computer
{
public:
virtual void Run() = 0;
virtual void Stop() = 0;
virtual ~Computer() {}; /* without this, you do not call Laptop or Desktop destructor in this example! */
};
class Laptop: public Computer
{
public:
void Run() override {mHibernating = false;};
void Stop() override {mHibernating = true;};
virtual ~Laptop() {}; /* because we have virtual functions, we need virtual destructor */
private:
bool mHibernating; // Whether or not the machine is hibernating
};
class Desktop: public Computer
{
public:
void Run() override {mOn = true;};
void Stop() override {mOn = false;};
virtual ~Desktop() {};
private:
bool mOn; // Whether or not the machine has been turned on
};
實際的ComputerFactory類返回一個Computer,該類基於對物件的現實世界描述。
class ComputerFactory
{
public:
static Computer *NewComputer(const std::string &description)
{
if(description == "laptop")
return new Laptop;
if(description == "desktop")
return new Desktop;
return nullptr;
}
};
讓我們分析一下這種設計的優勢。首先,有一個編譯優勢。如果我們將介面Computer與工廠一起移到一個單獨的標頭檔案中,那麼我們可以將NewComputer()函式的實現移到一個單獨的實現檔案中。現在,NewComputer()的實現檔案是唯一需要了解派生類的檔案。因此,如果對Computer的任何派生類進行了更改,或者添加了一個新的Computer子型別,則只有NewComputer()的實現檔案需要重新編譯。所有使用工廠的人都只會關心介面,該介面應該在應用程式的整個生命週期中保持一致。
此外,如果需要新增一個類,並且使用者透過使用者介面請求物件,則可能不需要更改呼叫工廠的任何程式碼以支援附加的計算機型別。使用工廠的程式碼只需將新字串傳遞給工廠,並允許工廠完全處理新型別。
想象一下程式設計一款影片遊戲,你希望將來新增新型別的敵人,每個敵人都有不同的 AI 功能,並且可以以不同的方式更新。透過使用工廠方法,程式的控制器可以呼叫工廠來建立敵人,而無需依賴或瞭解敵人的實際型別。現在,未來的開發人員可以建立新的敵人,使用新的 AI 控制元件和新的繪圖成員函式,將其新增到工廠中,並建立一個呼叫工廠的關卡,透過名稱請求敵人。將此方法與XML關卡描述相結合,開發人員可以建立新的關卡,而無需重新編譯他們的程式。所有這些,都要歸功於將物件的建立與物件的用法分離。
另一個例子
#include <stdexcept>
#include <iostream>
#include <memory>
using namespace std;
class Pizza {
public:
virtual int getPrice() const = 0;
virtual ~Pizza() {}; /* without this, no destructor for derived Pizza's will be called. */
};
class HamAndMushroomPizza : public Pizza {
public:
virtual int getPrice() const { return 850; };
virtual ~HamAndMushroomPizza() {};
};
class DeluxePizza : public Pizza {
public:
virtual int getPrice() const { return 1050; };
virtual ~DeluxePizza() {};
};
class HawaiianPizza : public Pizza {
public:
virtual int getPrice() const { return 1150; };
virtual ~HawaiianPizza() {};
};
class PizzaFactory {
public:
enum PizzaType {
HamMushroom,
Deluxe,
Hawaiian
};
static unique_ptr<Pizza> createPizza(PizzaType pizzaType) {
switch (pizzaType) {
case HamMushroom: return make_unique<HamAndMushroomPizza>();
case Deluxe: return make_unique<DeluxePizza>();
case Hawaiian: return make_unique<HawaiianPizza>();
}
throw "invalid pizza type.";
}
};
/*
* Create all available pizzas and print their prices
*/
void pizza_information(PizzaFactory::PizzaType pizzatype)
{
unique_ptr<Pizza> pizza = PizzaFactory::createPizza(pizzatype);
cout << "Price of " << pizzatype << " is " << pizza->getPrice() << std::endl;
}
int main()
{
pizza_information(PizzaFactory::HamMushroom);
pizza_information(PizzaFactory::Deluxe);
pizza_information(PizzaFactory::Hawaiian);
}
原型
[edit | edit source]當要建立的物件型別由原型例項決定時,在軟體開發中使用原型模式,原型例項被克隆以生成新的物件。例如,當以標準方式(例如,使用new關鍵字)建立新物件的固有成本對給定應用程式來說過高時,會使用這種模式。
實現:宣告一個抽象基類,該類指定一個純虛擬函式clone()方法。任何需要“多型建構函式”功能的類都從抽象基類派生而來,並實現clone()操作。
這裡,客戶端程式碼首先呼叫工廠方法。此工廠方法根據引數確定具體的類。在此具體類上,呼叫clone()方法,並由工廠方法返回該物件。
- 這是原型方法的示例實現。我們這裡有所有元件的詳細描述。
/** Implementation of Prototype Method **/
#include <iostream>
#include <unordered_map>
#include <string>
#include <memory>
using namespace std;
/** Record is the base Prototype */
class Record
{
public:
virtual ~Record() {}
virtual void print() = 0;
virtual unique_ptr<Record> clone() = 0;
};
/** CarRecord is a Concrete Prototype */
class CarRecord : public Record
{
private:
string m_carName;
int m_ID;
public:
CarRecord(string carName, int ID) : m_carName(carName), m_ID(ID)
{
}
void print() override
{
cout << "Car Record" << endl
<< "Name : " << m_carName << endl
<< "Number: " << m_ID << endl << endl;
}
unique_ptr<Record> clone() override
{
return make_unique<CarRecord>(*this);
}
};
/** BikeRecord is the Concrete Prototype */
class BikeRecord : public Record
{
private:
string m_bikeName;
int m_ID;
public:
BikeRecord(string bikeName, int ID) : m_bikeName(bikeName), m_ID(ID)
{
}
void print() override
{
cout << "Bike Record" << endl
<< "Name : " << m_bikeName << endl
<< "Number: " << m_ID << endl << endl;
}
unique_ptr<Record> clone() override
{
return make_unique<BikeRecord>(*this);
}
};
/** PersonRecord is the Concrete Prototype */
class PersonRecord : public Record
{
private:
string m_personName;
int m_age;
public:
PersonRecord(string personName, int age) : m_personName(personName), m_age(age)
{
}
void print() override
{
cout << "Person Record" << endl
<< "Name : " << m_personName << endl
<< "Age : " << m_age << endl << endl;
}
unique_ptr<Record> clone() override
{
return make_unique<PersonRecord>(*this);
}
};
/** Opaque record type, avoids exposing concrete implementations */
enum RecordType
{
CAR,
BIKE,
PERSON
};
/** RecordFactory is the client */
class RecordFactory
{
private:
unordered_map<RecordType, unique_ptr<Record>, hash<int> > m_records;
public:
RecordFactory()
{
m_records[CAR] = make_unique<CarRecord>("Ferrari", 5050);
m_records[BIKE] = make_unique<BikeRecord>("Yamaha", 2525);
m_records[PERSON] = make_unique<PersonRecord>("Tom", 25);
}
unique_ptr<Record> createRecord(RecordType recordType)
{
return m_records[recordType]->clone();
}
};
int main()
{
RecordFactory recordFactory;
auto record = recordFactory.createRecord(CAR);
record->print();
record = recordFactory.createRecord(BIKE);
record->print();
record = recordFactory.createRecord(PERSON);
record->print();
}
另一個例子
要實現該模式,請宣告一個抽象基類,該類指定一個純虛擬函式clone()成員函式。任何需要“多型建構函式”功能的類都從抽象基類派生而來,並實現clone()操作。
客戶端,而不是編寫呼叫new運算子在硬編碼類名上呼叫new運算子的程式碼,而是呼叫原型上的clone()成員函式,呼叫帶有指定所需特定具體派生類的引數的工廠成員函式,或者透過另一個設計模式提供的某些機制呼叫clone()成員函式。
class CPrototypeMonster
{
protected:
CString _name;
public:
CPrototypeMonster();
CPrototypeMonster( const CPrototypeMonster& copy );
virtual ~CPrototypeMonster();
virtual CPrototypeMonster* Clone() const=0; // This forces every derived class to provide an override for this function.
void Name( CString name );
CString Name() const;
};
class CGreenMonster : public CPrototypeMonster
{
protected:
int _numberOfArms;
double _slimeAvailable;
public:
CGreenMonster();
CGreenMonster( const CGreenMonster& copy );
~CGreenMonster();
virtual CPrototypeMonster* Clone() const;
void NumberOfArms( int numberOfArms );
void SlimeAvailable( double slimeAvailable );
int NumberOfArms() const;
double SlimeAvailable() const;
};
class CPurpleMonster : public CPrototypeMonster
{
protected:
int _intensityOfBadBreath;
double _lengthOfWhiplikeAntenna;
public:
CPurpleMonster();
CPurpleMonster( const CPurpleMonster& copy );
~CPurpleMonster();
virtual CPrototypeMonster* Clone() const;
void IntensityOfBadBreath( int intensityOfBadBreath );
void LengthOfWhiplikeAntenna( double lengthOfWhiplikeAntenna );
int IntensityOfBadBreath() const;
double LengthOfWhiplikeAntenna() const;
};
class CBellyMonster : public CPrototypeMonster
{
protected:
double _roomAvailableInBelly;
public:
CBellyMonster();
CBellyMonster( const CBellyMonster& copy );
~CBellyMonster();
virtual CPrototypeMonster* Clone() const;
void RoomAvailableInBelly( double roomAvailableInBelly );
double RoomAvailableInBelly() const;
};
CPrototypeMonster* CGreenMonster::Clone() const
{
return new CGreenMonster(*this);
}
CPrototypeMonster* CPurpleMonster::Clone() const
{
return new CPurpleMonster(*this);
}
CPrototypeMonster* CBellyMonster::Clone() const
{
return new CBellyMonster(*this);
}
具體怪物類的客戶端只需要對CPrototypeMonster類物件的引用(指標)才能能夠呼叫“Clone”函式並建立該物件的副本。下面的函式演示了這個概念
void DoSomeStuffWithAMonster( const CPrototypeMonster* originalMonster )
{
CPrototypeMonster* newMonster = originalMonster->Clone();
ASSERT( newMonster );
newMonster->Name("MyOwnMonster");
// Add code doing all sorts of cool stuff with the monster.
delete newMonster;
}
現在,originalMonster 可以作為指向 CGreenMonster、CPurpleMonster 或 CBellyMonster 的指標傳遞。
單例
[edit | edit source]單例模式確保類只有一個例項,並提供對該例項的全域性訪問點。它以單例集命名,單例集被定義為包含一個元素的集合。當需要一個物件來協調整個系統的操作時,這很有用。
檢查清單
- 在“單個例項”類中定義一個私有靜態屬性。
- 在類中定義一個公共靜態訪問器函式。
- 在訪問器函式中執行“延遲初始化”(首次使用時建立)。
- 將所有建構函式定義為受保護或私有。
- 客戶端只能使用訪問器函式來操作單例。
讓我們看一下單例與其他變數型別有何不同。
與全域性變數類似,單例存在於任何函式的範圍之外。傳統的實現使用單例類的靜態成員函式,該函式將在第一次呼叫時建立一個單例類的單個例項,並永遠返回該例項。以下程式碼示例說明了 C++ 單例類的元素,該類只儲存一個字串。
class StringSingleton
{
public:
// Some accessor functions for the class, itself
std::string GetString() const
{return mString;}
void SetString(const std::string &newStr)
{mString = newStr;}
// The magic function, which allows access to the class from anywhere
// To get the value of the instance of the class, call:
// StringSingleton::Instance().GetString();
static StringSingleton &Instance()
{
// This line only runs once, thus creating the only instance in existence
static std::auto_ptr<StringSingleton> instance( new StringSingleton );
// dereferencing the variable here, saves the caller from having to use
// the arrow operator, and removes temptation to try and delete the
// returned instance.
return *instance; // always returns the same instance
}
private:
// We need to make some given functions private to finish the definition of the singleton
StringSingleton(){} // default constructor available only to members or friends of this class
// Note that the next two functions are not given bodies, thus any attempt
// to call them implicitly will return as compiler errors. This prevents
// accidental copying of the only instance of the class.
StringSingleton(const StringSingleton &old); // disallow copy constructor
const StringSingleton &operator=(const StringSingleton &old); //disallow assignment operator
// Note that although this should be allowed,
// some compilers may not implement private destructors
// This prevents others from deleting our one single instance, which was otherwise created on the heap
~StringSingleton(){}
private: // private data for an instance of this class
std::string mString;
};
單例的變體
單例類的應用
單例設計模式的一個常見用途是應用程式配置。配置可能需要全域性訪問,並且可能需要對應用程式配置進行未來擴充套件。C 的最接近的替代方案是建立一個全域性struct。這缺乏關於此物件在何處例項化的清晰度,並且不能保證該物件的存在。
例如,考慮另一個開發人員在他們物件的建構函式中使用你的單例的情況。然後,另一個開發人員決定在全域性範圍內建立一個第二個類的例項。如果你只是使用了一個全域性變數,那麼連結的順序就會很重要。由於你的全域性變數將被訪問,可能在 main 開始執行之前,因此沒有定義全域性變數是否被初始化,或者第二個類的建構函式是否首先被呼叫。這種行為可能會隨著對程式碼其他區域的細微修改而改變,這將改變全域性程式碼執行的順序。這種錯誤可能非常難以除錯。但是,使用單例,第一次訪問物件時,物件也將被建立。你現在有一個物件,該物件將始終存在,與被使用有關,並且如果從未使用過,則將永遠不存在。
此類的第二個常見用途是將舊程式碼更新為在新的體系結構中工作。由於開發人員可能廣泛使用了全域性變數,因此將它們移到一個單獨的類中並將其設為單例,可以作為將程式與更強大的面向物件結構保持一致的中間步驟。
另一個例子
#include <iostream>
using namespace std;
/* Place holder for thread synchronization mutex */
class Mutex
{ /* placeholder for code to create, use, and free a mutex */
};
/* Place holder for thread synchronization lock */
class Lock
{ public:
Lock(Mutex& m) : mutex(m) { /* placeholder code to acquire the mutex */ }
~Lock() { /* placeholder code to release the mutex */ }
private:
Mutex & mutex;
};
class Singleton
{ public:
static Singleton* GetInstance();
int a;
~Singleton() { cout << "In Destructor" << endl; }
private:
Singleton(int _a) : a(_a) { cout << "In Constructor" << endl; }
static Mutex mutex;
// Not defined, to prevent copying
Singleton(const Singleton& );
Singleton& operator =(const Singleton& other);
};
Mutex Singleton::mutex;
Singleton* Singleton::GetInstance()
{
Lock lock(mutex);
cout << "Get Instance" << endl;
// Initialized during first access
static Singleton inst(1);
return &inst;
}
int main()
{
Singleton* singleton = Singleton::GetInstance();
cout << "The value of the singleton: " << singleton->a << endl;
return 0;
}
結構型模式
[edit | edit source]介面卡
[edit | edit source]將類的介面轉換為客戶端期望的另一個介面。介面卡允許類協同工作,否則由於介面不相容而無法協同工作。
#include <iostream>
class Dog { // Abstract Target
public:
virtual ~Dog() = default;
virtual void performsConversion() const = 0;
};
class DogFemale : public Dog { // Concrete Target
public:
virtual void performsConversion() const override { std::cout << "Dog female performs conversion." << std::endl; }
};
class Cat { // Abstract Adaptee
public:
virtual ~Cat() = default;
virtual void performsConversion() const = 0;
};
class CatFemale : public Cat { // Concrete Adaptee
public:
virtual void performsConversion() const override { std::cout << "Cat female performs conversion." << std::endl; }
};
class DogNature {
public:
void carryOutNature(Dog* dog) {
std::cout << "On with the Dog nature!" << std::endl;
dog->performsConversion();
}
};
class ConversionAdapter : public Dog { // Adapter
private:
Cat* cat;
public:
ConversionAdapter(Cat* c) : cat(c) {}
virtual void performsConversion() const override { cat->performsConversion(); }
};
int main() { // Client code
DogFemale* dogFemale = new DogFemale;
CatFemale* catFemale = new CatFemale;
DogNature dogNature;
// dogNature.carryOutNature (catFemale); // Will not compile of course since the parameter must be of type Dog*.
ConversionAdapter* adaptedCat = new ConversionAdapter(catFemale); // catFemale has adapted to become a Dog!
dogNature.carryOutNature(dogFemale);
dogNature.carryOutNature(adaptedCat); // So now catFemale, in the form of adaptedCat, participates in the dogNature!
// Note that catFemale is carrying out her own type of nature in dogNature though.
delete adaptedCat; // adaptedCat is not needed anymore
delete catFemale; // catFemale is not needed anymore
delete dogFemale; // dogFemale is not needed anymore, too
return 0;
}
橋接
[edit | edit source]橋接模式用於將介面與其實現分離。這樣做提供了靈活性,以便兩者可以獨立地變化。
以下示例將輸出
API1.circle at 1:2 7.5 API2.circle at 5:7 27.5
#include <iostream>
using namespace std;
/* Implementor*/
class DrawingAPI {
public:
virtual void drawCircle(double x, double y, double radius) = 0;
virtual ~DrawingAPI() {}
};
/* Concrete ImplementorA*/
class DrawingAPI1 : public DrawingAPI {
public:
void drawCircle(double x, double y, double radius) {
cout << "API1.circle at " << x << ':' << y << ' ' << radius << endl;
}
};
/* Concrete ImplementorB*/
class DrawingAPI2 : public DrawingAPI {
public:
void drawCircle(double x, double y, double radius) {
cout << "API2.circle at " << x << ':' << y << ' ' << radius << endl;
}
};
/* Abstraction*/
class Shape {
public:
virtual ~Shape() {}
virtual void draw() = 0;
virtual void resizeByPercentage(double pct) = 0;
};
/* Refined Abstraction*/
class CircleShape : public Shape {
public:
CircleShape(double x, double y,double radius, DrawingAPI *drawingAPI) :
m_x(x), m_y(y), m_radius(radius), m_drawingAPI(drawingAPI)
{}
void draw() {
m_drawingAPI->drawCircle(m_x, m_y, m_radius);
}
void resizeByPercentage(double pct) {
m_radius *= pct;
}
private:
double m_x, m_y, m_radius;
DrawingAPI *m_drawingAPI;
};
int main(void) {
CircleShape circle1(1,2,3,new DrawingAPI1());
CircleShape circle2(5,7,11,new DrawingAPI2());
circle1.resizeByPercentage(2.5);
circle2.resizeByPercentage(2.5);
circle1.draw();
circle2.draw();
return 0;
}
組合模式允許客戶端以統一的方式處理單個物件和物件的組合。組合模式可以表示這兩種情況。在這個模式中,可以開發樹形結構來表示部分-整體層次結構。
#include <vector>
#include <iostream> // std::cout
#include <memory> // std::auto_ptr
#include <algorithm> // std::for_each
using namespace std;
class Graphic
{
public:
virtual void print() const = 0;
virtual ~Graphic() {}
};
class Ellipse : public Graphic
{
public:
void print() const {
cout << "Ellipse \n";
}
};
class CompositeGraphic : public Graphic
{
public:
void print() const {
for(Graphic * a: graphicList_) {
a->print();
}
}
void add(Graphic *aGraphic) {
graphicList_.push_back(aGraphic);
}
private:
vector<Graphic*> graphicList_;
};
int main()
{
// Initialize four ellipses
const auto_ptr<Ellipse> ellipse1(new Ellipse());
const auto_ptr<Ellipse> ellipse2(new Ellipse());
const auto_ptr<Ellipse> ellipse3(new Ellipse());
const auto_ptr<Ellipse> ellipse4(new Ellipse());
// Initialize three composite graphics
const auto_ptr<CompositeGraphic> graphic(new CompositeGraphic());
const auto_ptr<CompositeGraphic> graphic1(new CompositeGraphic());
const auto_ptr<CompositeGraphic> graphic2(new CompositeGraphic());
// Composes the graphics
graphic1->add(ellipse1.get());
graphic1->add(ellipse2.get());
graphic1->add(ellipse3.get());
graphic2->add(ellipse4.get());
graphic->add(graphic1.get());
graphic->add(graphic2.get());
// Prints the complete graphic (four times the string "Ellipse")
graphic->print();
return 0;
}
裝飾器模式有助於動態地將附加的行為或職責附加到物件。裝飾器為擴充套件功能提供了一種靈活的替代子類化的方法。這也稱為“包裝器”。如果您的應用程式執行某種型別的過濾,那麼裝飾器可能是考慮用於該任務的良好模式。
#include <string>
#include <iostream>
using namespace std;
class Car //Our Abstract base class
{
protected:
string _str;
public:
Car()
{
_str = "Unknown Car";
}
virtual string getDescription()
{
return _str;
}
virtual double getCost() = 0;
virtual ~Car()
{
cout << "~Car()\n";
}
};
class OptionsDecorator : public Car //Decorator Base class
{
public:
virtual string getDescription() = 0;
virtual ~OptionsDecorator()
{
cout<<"~OptionsDecorator()\n";
}
};
class CarModel1 : public Car
{
public:
CarModel1()
{
_str = "CarModel1";
}
virtual double getCost()
{
return 31000.23;
}
~CarModel1()
{
cout<<"~CarModel1()\n";
}
};
class Navigation: public OptionsDecorator
{
Car *_b;
public:
Navigation(Car *b)
{
_b = b;
}
string getDescription()
{
return _b->getDescription() + ", Navigation";
}
double getCost()
{
return 300.56 + _b->getCost();
}
~Navigation()
{
cout << "~Navigation()\n";
delete _b;
}
};
class PremiumSoundSystem: public OptionsDecorator
{
Car *_b;
public:
PremiumSoundSystem(Car *b)
{
_b = b;
}
string getDescription()
{
return _b->getDescription() + ", PremiumSoundSystem";
}
double getCost()
{
return 0.30 + _b->getCost();
}
~PremiumSoundSystem()
{
cout << "~PremiumSoundSystem()\n";
delete _b;
}
};
class ManualTransmission: public OptionsDecorator
{
Car *_b;
public:
ManualTransmission(Car *b)
{
_b = b;
}
string getDescription()
{
return _b->getDescription()+ ", ManualTransmission";
}
double getCost()
{
return 0.30 + _b->getCost();
}
~ManualTransmission()
{
cout << "~ManualTransmission()\n";
delete _b;
}
};
int main()
{
//Create our Car that we want to buy
Car *b = new CarModel1();
cout << "Base model of " << b->getDescription() << " costs $" << b->getCost() << "\n";
//Who wants base model let's add some more features
b = new Navigation(b);
cout << b->getDescription() << " will cost you $" << b->getCost() << "\n";
b = new PremiumSoundSystem(b);
b = new ManualTransmission(b);
cout << b->getDescription() << " will cost you $" << b->getCost() << "\n";
// WARNING! Here we leak the CarModel1, Navigation and PremiumSoundSystem objects!
// Either we delete them explicitly or rewrite the Decorators to take
// ownership and delete their Cars when destroyed.
delete b;
return 0;
}
上面程式的輸出是
Base model of CarModel1 costs $31000.2
CarModel1, Navigation will cost you $31300.8
CarModel1, Navigation, PremiumSoundSystem, ManualTransmission will cost you $31301.4
~ManualTransmission
~PremiumSoundSystem()
~Navigation()
~CarModel1
~Car()
~OptionsDecorator()
~Car()
~OptionsDecorator()
~Car()
~OptionsDecorator()
~Car()
另一個例子(C++14)
#include <iostream>
#include <string>
#include <memory>
class Interface {
public:
virtual ~Interface() { }
virtual void write (std::string&) = 0;
};
class Core : public Interface {
public:
~Core() {std::cout << "Core destructor called.\n";}
virtual void write (std::string& text) override {}; // Do nothing.
};
class Decorator : public Interface {
private:
std::unique_ptr<Interface> interface;
public:
Decorator (std::unique_ptr<Interface> c) {interface = std::move(c);}
virtual void write (std::string& text) override {interface->write(text);}
};
class MessengerWithSalutation : public Decorator {
private:
std::string salutation;
public:
MessengerWithSalutation (std::unique_ptr<Interface> c, const std::string& str) : Decorator(std::move(c)), salutation(str) {}
~MessengerWithSalutation() {std::cout << "Messenger destructor called.\n";}
virtual void write (std::string& text) override {
text = salutation + "\n\n" + text;
Decorator::write(text);
}
};
class MessengerWithValediction : public Decorator {
private:
std::string valediction;
public:
MessengerWithValediction (std::unique_ptr<Interface> c, const std::string& str) : Decorator(std::move(c)), valediction(str) {}
~MessengerWithValediction() {std::cout << "MessengerWithValediction destructor called.\n";}
virtual void write (std::string& text) override {
Decorator::write(text);
text += "\n\n" + valediction;
}
};
int main() {
const std::string salutation = "Greetings,";
const std::string valediction = "Sincerly, Andy";
std::string message1 = "This message is not decorated.";
std::string message2 = "This message is decorated with a salutation.";
std::string message3 = "This message is decorated with a valediction.";
std::string message4 = "This message is decorated with a salutation and a valediction.";
std::unique_ptr<Interface> messenger1 = std::make_unique<Core>();
std::unique_ptr<Interface> messenger2 = std::make_unique<MessengerWithSalutation> (std::make_unique<Core>(), salutation);
std::unique_ptr<Interface> messenger3 = std::make_unique<MessengerWithValediction> (std::make_unique<Core>(), valediction);
std::unique_ptr<Interface> messenger4 = std::make_unique<MessengerWithValediction> (std::make_unique<MessengerWithSalutation>
(std::make_unique<Core>(), salutation), valediction);
messenger1->write(message1);
std::cout << message1 << '\n';
std::cout << "\n------------------------------\n\n";
messenger2->write(message2);
std::cout << message2 << '\n';
std::cout << "\n------------------------------\n\n";
messenger3->write(message3);
std::cout << message3 << '\n';
std::cout << "\n------------------------------\n\n";
messenger4->write(message4);
std::cout << message4 << '\n';
std::cout << "\n------------------------------\n\n";
}
上面程式的輸出是
This message is not decorated.
------------------------------
Greetings,
This message is decorated with a salutation.
------------------------------
This message is decorated with a valediction.
Sincerly, Andy
------------------------------
Greetings,
This message is decorated with a salutation and a valediction.
Sincerly, Andy
------------------------------
MessengerWithValediction destructor called.
Messenger destructor called.
Core destructor called.
MessengerWithValediction destructor called.
Core destructor called.
Messenger destructor called.
Core destructor called.
Core destructor called.
外觀模式透過向客戶端提供一個介面來隱藏系統的複雜性,客戶端可以透過該介面以統一的介面訪問系統。外觀定義了一個更高層的介面,使子系統更容易使用。例如,使一個類方法透過呼叫其他幾個類來執行復雜的過程。
/*Facade is one of the easiest patterns I think... And this is very simple example.
Imagine you set up a smart house where everything is on remote. So to turn the lights on you push lights on button - And same for TV,
AC, Alarm, Music, etc...
When you leave a house you would need to push a 100 buttons to make sure everything is off and are good to go which could be little
annoying if you are lazy like me
so I defined a Facade for leaving and coming back. (Facade functions represent buttons...) So when I come and leave I just make one
call and it takes care of everything...
*/
#include <string>
#include <iostream>
using namespace std;
class Alarm
{
public:
void alarmOn()
{
cout << "Alarm is on and house is secured"<<endl;
}
void alarmOff()
{
cout << "Alarm is off and you can go into the house"<<endl;
}
};
class Ac
{
public:
void acOn()
{
cout << "Ac is on"<<endl;
}
void acOff()
{
cout << "AC is off"<<endl;
}
};
class Tv
{
public:
void tvOn()
{
cout << "Tv is on"<<endl;
}
void tvOff()
{
cout << "TV is off"<<endl;
}
};
class HouseFacade
{
Alarm alarm;
Ac ac;
Tv tv;
public:
HouseFacade(){}
void goToWork()
{
ac.acOff();
tv.tvOff();
alarm.alarmOn();
}
void comeHome()
{
alarm.alarmOff();
ac.acOn();
tv.tvOn();
}
};
int main()
{
HouseFacade hf;
//Rather than calling 100 different on and off functions thanks to facade I only have 2 functions...
hf.goToWork();
hf.comeHome();
}
上面程式的輸出是
AC is off TV is off Alarm is on and house is secured Alarm is off and you can go into the house Ac is on Tv is on
透過共享物件的屬性來節省記憶體(基本上)的模式。想象一下大量的類似物件,它們的大多數屬性都是相同的。很自然地將這些屬性從這些物件中移到一些外部資料結構中,併為每個物件提供指向該資料結構的連結。
#include <iostream>
#include <string>
#include <vector>
#define NUMBER_OF_SAME_TYPE_CHARS 3;
/* Actual flyweight objects class (declaration) */
class FlyweightCharacter;
/*
FlyweightCharacterAbstractBuilder is a class holding the properties which are shared by
many objects. So instead of keeping these properties in those objects we keep them externally, making
objects flyweight. See more details in the comments of main function.
*/
class FlyweightCharacterAbstractBuilder {
FlyweightCharacterAbstractBuilder() {}
~FlyweightCharacterAbstractBuilder() {}
public:
static std::vector<float> fontSizes; // lets imagine that sizes may be of floating point type
static std::vector<std::string> fontNames; // font name may be of variable length (lets take 6 bytes is average)
static void setFontsAndNames();
static FlyweightCharacter createFlyweightCharacter(unsigned short fontSizeIndex,
unsigned short fontNameIndex,
unsigned short positionInStream);
};
std::vector<float> FlyweightCharacterAbstractBuilder::fontSizes(3);
std::vector<std::string> FlyweightCharacterAbstractBuilder::fontNames(3);
void FlyweightCharacterAbstractBuilder::setFontsAndNames() {
fontSizes[0] = 1.0;
fontSizes[1] = 1.5;
fontSizes[2] = 2.0;
fontNames[0] = "first_font";
fontNames[1] = "second_font";
fontNames[2] = "third_font";
}
class FlyweightCharacter {
unsigned short fontSizeIndex; // index instead of actual font size
unsigned short fontNameIndex; // index instead of font name
unsigned positionInStream;
public:
FlyweightCharacter(unsigned short fontSizeIndex, unsigned short fontNameIndex, unsigned short positionInStream):
fontSizeIndex(fontSizeIndex), fontNameIndex(fontNameIndex), positionInStream(positionInStream) {}
void print() {
std::cout << "Font Size: " << FlyweightCharacterAbstractBuilder::fontSizes[fontSizeIndex]
<< ", font Name: " << FlyweightCharacterAbstractBuilder::fontNames[fontNameIndex]
<< ", character stream position: " << positionInStream << std::endl;
}
~FlyweightCharacter() {}
};
FlyweightCharacter FlyweightCharacterAbstractBuilder::createFlyweightCharacter(unsigned short fontSizeIndex, unsigned short fontNameIndex, unsigned short positionInStream) {
FlyweightCharacter fc(fontSizeIndex, fontNameIndex, positionInStream);
return fc;
}
int main(int argc, char** argv) {
std::vector<FlyweightCharacter> chars;
FlyweightCharacterAbstractBuilder::setFontsAndNames();
unsigned short limit = NUMBER_OF_SAME_TYPE_CHARS;
for (unsigned short i = 0; i < limit; i++) {
chars.push_back(FlyweightCharacterAbstractBuilder::createFlyweightCharacter(0, 0, i));
chars.push_back(FlyweightCharacterAbstractBuilder::createFlyweightCharacter(1, 1, i + 1 * limit));
chars.push_back(FlyweightCharacterAbstractBuilder::createFlyweightCharacter(2, 2, i + 2 * limit));
}
/*
Each char stores links to its fontName and fontSize so what we get is:
each object instead of allocating 6 bytes (convention above) for string
and 4 bytes for float allocates 2 bytes for fontNameIndex and fontSizeIndex.
That means for each char we save 6 + 4 - 2 - 2 = 6 bytes.
Now imagine we have NUMBER_OF_SAME_TYPE_CHARS = 1000 i.e. with our code
we will have 3 groups of chars with 1000 chars in each group which will save
3 * 1000 * 6 - (3 * 6 + 3 * 4) = 17970 saved bytes.
3 * 6 + 3 * 4 is a number of bytes allocated by FlyweightCharacterAbstractBuilder.
So the idea of the pattern is to move properties shared by many objects to some
external container. The objects in that case don't store the data themselves they
store only links to the data which saves memory and make the objects lighter.
The data size of properties stored externally may be significant which will save REALLY
huge amount of memory and will make each object super light in comparison to its counterpart.
That's where the name of the pattern comes from: flyweight (i.e. very light).
*/
for (unsigned short i = 0; i < chars.size(); i++) {
chars[i].print();
}
std::cin.get(); return 0;
}
代理模式將為一個物件提供另一個物件的代理或佔位符,以控制對其的訪問。它用於在需要用更簡單的物件表示複雜物件時使用。如果物件的建立很昂貴,則可以將其推遲到真正需要時,而在此期間,一個更簡單的物件可以充當佔位符。這個佔位符物件稱為複雜物件的“代理”。
#include <iostream>
#include <memory>
class ICar {
public:
virtual ~ICar() { std::cout << "ICar destructor!" << std::endl; }
virtual void DriveCar() = 0;
};
class Car : public ICar {
public:
void DriveCar() override { std::cout << "Car has been driven!" << std::endl; }
};
class ProxyCar : public ICar {
public:
ProxyCar(int driver_age) : driver_age_(driver_age) {}
void DriveCar() override {
if (driver_age_ > 16) {
real_car_->DriveCar();
} else {
std::cout << "Sorry, the driver is too young to drive." << std::endl;
}
}
private:
std::unique_ptr<ICar> real_car_ = std::make_unique<Car>();
int driver_age_;
};
int main() {
std::unique_ptr<ICar> car = std::make_unique<ProxyCar>(16);
car->DriveCar();
car = std::make_unique<ProxyCar>(25);
car->DriveCar();
return 0;
}
這種技術更廣為人知的是Mixin。Mixin 在文獻中被描述為表達抽象的強大工具[需要引用]。
基於介面的程式設計與模組化程式設計和麵向物件程式設計密切相關,它將應用程式定義為相互耦合的模組(相互連線並透過介面相互插入)的集合。模組可以被拔掉、替換或升級,而無需損害其他模組的內容。
整個系統的複雜性大大降低。基於介面的程式設計在模組化程式設計的基礎上更進一步,它堅持要求為這些模組新增介面。因此,整個系統被視為元件以及幫助它們協同工作的介面。
基於介面的程式設計增加了應用程式的模組化,從而提高了其在後期開發週期中的可維護性,尤其是在每個模組都必須由不同的團隊開發的情況下。這是一種眾所周知的、已經存在很長時間的方法,它是諸如 CORBA 之類框架背後的核心技術。 [需要引用]
當第三方為已建立的系統開發額外的元件時,這尤其方便。他們只需要開發滿足父應用程式供應商指定的介面的元件即可。
因此,介面的釋出者保證他不會更改介面,而訂閱者同意完整地實現介面,沒有任何偏差。因此,介面被稱為契約協議,基於此的程式設計正規化被稱為“基於介面的程式設計”。
責任鏈模式的目的是透過讓多個物件有機會處理請求來避免將請求傳送方與接收方耦合。它將接收物件連結起來,並將請求沿著鏈傳遞,直到某個物件處理它。
#include <iostream>
using namespace std;
class Handler {
protected:
Handler *next;
public:
Handler() {
next = NULL;
}
virtual ~Handler() { }
virtual void request(int value) = 0;
void setNextHandler(Handler *nextInLine) {
next = nextInLine;
}
};
class SpecialHandler : public Handler {
private:
int myLimit;
int myId;
public:
SpecialHandler(int limit, int id) {
myLimit = limit;
myId = id;
}
~SpecialHandler() { }
void request(int value) {
if(value < myLimit) {
cout << "Handler " << myId << " handled the request with a limit of " << myLimit << endl;
} else if(next != NULL) {
next->request(value);
} else {
cout << "Sorry, I am the last handler (" << myId << ") and I can't handle the request." << endl;
}
}
};
int main () {
Handler *h1 = new SpecialHandler(10, 1);
Handler *h2 = new SpecialHandler(20, 2);
Handler *h3 = new SpecialHandler(30, 3);
h1->setNextHandler(h2);
h2->setNextHandler(h3);
h1->request(18);
h1->request(40);
delete h1;
delete h2;
delete h3;
return 0;
}
命令模式是一種物件行為模式,它透過將請求封裝為物件來解耦傳送方和接收方,從而允許您使用不同的請求引數化客戶端,對請求進行排隊或記錄,並支援可撤消的操作。它也可以被認為是回撥方法的面向物件等效物。
回撥:它是一個在基於使用者操作的稍後時間點註冊要呼叫的函式。
#include <iostream>
using namespace std;
/*the Command interface*/
class Command
{
public:
virtual void execute()=0;
};
/*Receiver class*/
class Light {
public:
Light() { }
void turnOn()
{
cout << "The light is on" << endl;
}
void turnOff()
{
cout << "The light is off" << endl;
}
};
/*the Command for turning on the light*/
class FlipUpCommand: public Command
{
public:
FlipUpCommand(Light& light):theLight(light)
{
}
virtual void execute()
{
theLight.turnOn();
}
private:
Light& theLight;
};
/*the Command for turning off the light*/
class FlipDownCommand: public Command
{
public:
FlipDownCommand(Light& light) :theLight(light)
{
}
virtual void execute()
{
theLight.turnOff();
}
private:
Light& theLight;
};
class Switch {
public:
Switch(Command& flipUpCmd, Command& flipDownCmd)
:flipUpCommand(flipUpCmd),flipDownCommand(flipDownCmd)
{
}
void flipUp()
{
flipUpCommand.execute();
}
void flipDown()
{
flipDownCommand.execute();
}
private:
Command& flipUpCommand;
Command& flipDownCommand;
};
/*The test class or client*/
int main()
{
Light lamp;
FlipUpCommand switchUp(lamp);
FlipDownCommand switchDown(lamp);
Switch s(switchUp, switchDown);
s.flipUp();
s.flipDown();
}
給定一種語言,定義其語法的表示以及一個直譯器,該直譯器使用該表示來解釋該語言中的句子。
#include <iostream>
#include <string>
#include <map>
#include <list>
namespace wikibooks_design_patterns
{
// based on the Java sample around here
typedef std::string String;
struct Expression;
typedef std::map<String,Expression*> Map;
typedef std::list<Expression*> Stack;
struct Expression {
virtual int interpret(Map variables) = 0;
virtual ~Expression() {}
};
class Number : public Expression {
private:
int number;
public:
Number(int number) { this->number = number; }
int interpret(Map variables) { return number; }
};
class Plus : public Expression {
Expression* leftOperand;
Expression* rightOperand;
public:
Plus(Expression* left, Expression* right) {
leftOperand = left;
rightOperand = right;
}
~Plus(){
delete leftOperand;
delete rightOperand;
}
int interpret(Map variables) {
return leftOperand->interpret(variables) + rightOperand->interpret(variables);
}
};
class Minus : public Expression {
Expression* leftOperand;
Expression* rightOperand;
public:
Minus(Expression* left, Expression* right) {
leftOperand = left;
rightOperand = right;
}
~Minus(){
delete leftOperand;
delete rightOperand;
}
int interpret(Map variables) {
return leftOperand->interpret(variables) - rightOperand->interpret(variables);
}
};
class Variable : public Expression {
String name;
public:
Variable(String name) { this->name = name; }
int interpret(Map variables) {
if(variables.end() == variables.find(name)) return 0;
return variables[name]->interpret(variables);
}
};
// While the interpreter pattern does not address parsing, a parser is provided for completeness.
class Evaluator : public Expression {
Expression* syntaxTree;
public:
Evaluator(String expression){
Stack expressionStack;
size_t last = 0;
for (size_t next = 0; String::npos != last; last = (String::npos == next) ? next : (1+next)) {
next = expression.find(' ', last);
String token( expression.substr(last, (String::npos == next) ? (expression.length()-last) : (next-last)));
if (token == "+") {
Expression* right = expressionStack.back(); expressionStack.pop_back();
Expression* left = expressionStack.back(); expressionStack.pop_back();
Expression* subExpression = new Plus(right, left);
expressionStack.push_back( subExpression );
}
else if (token == "-") {
// it's necessary remove first the right operand from the stack
Expression* right = expressionStack.back(); expressionStack.pop_back();
// ..and after the left one
Expression* left = expressionStack.back(); expressionStack.pop_back();
Expression* subExpression = new Minus(left, right);
expressionStack.push_back( subExpression );
}
else
expressionStack.push_back( new Variable(token) );
}
syntaxTree = expressionStack.back(); expressionStack.pop_back();
}
~Evaluator() {
delete syntaxTree;
}
int interpret(Map context) {
return syntaxTree->interpret(context);
}
};
}
void main()
{
using namespace wikibooks_design_patterns;
Evaluator sentence("w x z - +");
static
const int sequences[][3] = {
{5, 10, 42}, {1, 3, 2}, {7, 9, -5},
};
for (size_t i = 0; sizeof(sequences)/sizeof(sequences[0]) > i; ++i) {
Map variables;
variables["w"] = new Number(sequences[i][0]);
variables["x"] = new Number(sequences[i][1]);
variables["z"] = new Number(sequences[i][2]);
int result = sentence.interpret(variables);
for (Map::iterator it = variables.begin(); variables.end() != it; ++it) delete it->second;
std::cout<<"Interpreter result: "<<result<<std::endl;
}
}
“迭代器”設計模式在 STL 中被廣泛用於遍歷各種容器。充分理解這一點將使開發人員能夠建立高度可重用且易於理解的[需要引用]資料容器。
迭代器的基本思想是它允許遍歷容器(如指標在陣列中移動)。但是,要獲取容器的下一個元素,您不需要了解容器的構造方式。這是迭代器的工作。透過簡單地使用迭代器提供的成員函式,您可以按照容器的預期順序,從第一個元素移動到最後一個元素。
讓我們首先考慮一個傳統的單維陣列,其中一個指標從開始移動到結束。此示例假定您瞭解指標運算。請注意,從現在開始,使用“it”或“itr”是“iterator”的簡寫形式。
const int ARRAY_LEN = 42;
int *myArray = new int[ARRAY_LEN];
// Set the iterator to point to the first memory location of the array
int *arrayItr = myArray;
// Move through each element of the array, setting it equal to its position in the array
for(int i = 0; i < ARRAY_LEN; ++i)
{
// set the value of the current location in the array
*arrayItr = i;
// by incrementing the pointer, we move it to the next position in the array.
// This is easy for a contiguous memory container, since pointer arithmetic
// handles the traversal.
++arrayItr;
}
// Do not be messy, clean up after yourself
delete[] myArray;
此程式碼對陣列非常快,但是我們如何遍歷連結串列,因為記憶體不連續?考慮如下基本連結串列的實現
class IteratorCannotMoveToNext{}; // Error class
class MyIntLList
{
public:
// The Node class represents a single element in the linked list.
// The node has a next node and a previous node, so that the user
// may move from one position to the next, or step back a single
// position. Notice that the traversal of a linked list is O(N),
// as is searching, since the list is not ordered.
class Node
{
public:
Node():mNextNode(0),mPrevNode(0),mValue(0){}
Node *mNextNode;
Node *mPrevNode;
int mValue;
};
MyIntLList():mSize(0)
{}
~MyIntLList()
{
while(!Empty())
pop_front();
} // See expansion for further implementation;
int Size() const {return mSize;}
// Add this value to the end of the list
void push_back(int value)
{
Node *newNode = new Node;
newNode->mValue = value;
newNode->mPrevNode = mTail;
mTail->mNextNode = newNode;
mTail = newNode;
++mSize;
}
// Remove the value from the beginning of the list
void pop_front()
{
if(Empty())
return;
Node *tmpnode = mHead;
mHead = mHead->mNextNode;
delete tmpnode;
--mSize;
}
bool Empty()
{return mSize == 0;}
// This is where the iterator definition will go,
// but lets finish the definition of the list, first
private:
Node *mHead;
Node *mTail;
int mSize;
};
此連結串列的記憶體不連續,因此不能用於指標運算。而且我們不想將連結串列的內部結構暴露給其他開發人員,迫使他們學習它們,並阻止我們更改它。
這就是迭代器發揮作用的地方。公共介面使學習容器的用法變得更容易,並將遍歷邏輯隱藏在其他開發人員面前。
讓我們檢查一下迭代器本身的程式碼。
/*
* The iterator class knows the internals of the linked list, so that it
* may move from one element to the next. In this implementation, I have
* chosen the classic traversal method of overloading the increment
* operators. More thorough implementations of a bi-directional linked
* list would include decrement operators so that the iterator may move
* in the opposite direction.
*/
class Iterator
{
public:
Iterator(Node *position):mCurrNode(position){}
// Prefix increment
const Iterator &operator++()
{
if(mCurrNode == 0 || mCurrNode->mNextNode == 0)
throw IteratorCannotMoveToNext();e
mCurrNode = mCurrNode->mNextNode;
return *this;
}
// Postfix increment
Iterator operator++(int)
{
Iterator tempItr = *this;
++(*this);
return tempItr;
}
// Dereferencing operator returns the current node, which should then
// be dereferenced for the int. TODO: Check syntax for overloading
// dereferencing operator
Node * operator*()
{return mCurrNode;}
// TODO: implement arrow operator and clean up example usage following
private:
Node *mCurrNode;
};
// The following two functions make it possible to create
// iterators for an instance of this class.
// First position for iterators should be the first element in the container.
Iterator Begin(){return Iterator(mHead);}
// Final position for iterators should be one past the last element in the container.
Iterator End(){return Iterator(0);}
透過此實現,現在可以不瞭解容器的大小或其資料的組織方式,就可以按順序遍歷每個元素,操作或簡單地訪問資料。這透過 MyIntLList 類中的訪問器 Begin() 和 End() 來完成。
// Create a list
MyIntLList myList;
// Add some items to the list
for(int i = 0; i < 10; ++i)
myList.push_back(i);
// Move through the list, adding 42 to each item.
for(MyIntLList::Iterator it = myList.Begin(); it != myList.End(); ++it)
(*it)->mValue += 42;
以下程式給出了使用通用模板的迭代器設計模式的實現
/************************************************************************/
/* Iterator.h */
/************************************************************************/
#ifndef MY_ITERATOR_HEADER
#define MY_ITERATOR_HEADER
#include <iterator>
#include <vector>
#include <set>
//////////////////////////////////////////////////////////////////////////
template<class T, class U>
class Iterator
{
public:
typedef typename std::vector<T>::iterator iter_type;
Iterator(U *pData):m_pData(pData){
m_it = m_pData->m_data.begin();
}
void first()
{
m_it = m_pData->m_data.begin();
}
void next()
{
m_it++;
}
bool isDone()
{
return (m_it == m_pData->m_data.end());
}
iter_type current()
{
return m_it;
}
private:
U *m_pData;
iter_type m_it;
};
template<class T, class U, class A>
class setIterator
{
public:
typedef typename std::set<T,U>::iterator iter_type;
setIterator(A *pData):m_pData(pData)
{
m_it = m_pData->m_data.begin();
}
void first()
{
m_it = m_pData->m_data.begin();
}
void next()
{
m_it++;
}
bool isDone()
{
return (m_it == m_pData->m_data.end());
}
iter_type current()
{
return m_it;
}
private:
A *m_pData;
iter_type m_it;
};
#endif
/************************************************************************/
/* Aggregate.h */
/************************************************************************/
#ifndef MY_DATACOLLECTION_HEADER
#define MY_DATACOLLECTION_HEADER
#include "Iterator.h"
template <class T>
class aggregate
{
friend class Iterator<T, aggregate>;
public:
void add(T a)
{
m_data.push_back(a);
}
Iterator<T, aggregate> *create_iterator()
{
return new Iterator<T, aggregate>(this);
}
private:
std::vector<T> m_data;
};
template <class T, class U>
class aggregateSet
{
friend class setIterator<T, U, aggregateSet>;
public:
void add(T a)
{
m_data.insert(a);
}
setIterator<T, U, aggregateSet> *create_iterator()
{
return new setIterator<T,U,aggregateSet>(this);
}
void Print()
{
copy(m_data.begin(), m_data.end(), std::ostream_iterator<T>(std::cout, "\n"));
}
private:
std::set<T,U> m_data;
};
#endif
/************************************************************************/
/* Iterator Test.cpp */
/************************************************************************/
#include <iostream>
#include <string>
#include "Aggregate.h"
using namespace std;
class Money
{
public:
Money(int a = 0): m_data(a) {}
void SetMoney(int a)
{
m_data = a;
}
int GetMoney()
{
return m_data;
}
private:
int m_data;
};
class Name
{
public:
Name(string name): m_name(name) {}
const string &GetName() const
{
return m_name;
}
friend ostream &operator<<(ostream& out, Name name)
{
out << name.GetName();
return out;
}
private:
string m_name;
};
struct NameLess
{
bool operator()(const Name &lhs, const Name &rhs) const
{
return (lhs.GetName() < rhs.GetName());
}
};
int main()
{
//sample 1
cout << "________________Iterator with int______________________________________" << endl;
aggregate<int> agg;
for (int i = 0; i < 10; i++)
agg.add(i);
Iterator< int,aggregate<int> > *it = agg.create_iterator();
for(it->first(); !it->isDone(); it->next())
cout << *it->current() << endl;
//sample 2
aggregate<Money> agg2;
Money a(100), b(1000), c(10000);
agg2.add(a);
agg2.add(b);
agg2.add(c);
cout << "________________Iterator with Class Money______________________________" << endl;
Iterator<Money, aggregate<Money> > *it2 = agg2.create_iterator();
for (it2->first(); !it2->isDone(); it2->next())
cout << it2->current()->GetMoney() << endl;
//sample 3
cout << "________________Set Iterator with Class Name______________________________" << endl;
aggregateSet<Name, NameLess> aset;
aset.add(Name("Qmt"));
aset.add(Name("Bmt"));
aset.add(Name("Cmt"));
aset.add(Name("Amt"));
setIterator<Name, NameLess, aggregateSet<Name, NameLess> > *it3 = aset.create_iterator();
for (it3->first(); !it3->isDone(); it3->next())
cout << (*it3->current()) << endl;
}
控制檯輸出
________________Iterator with int______________________________________ 0 1 2 3 4 5 6 7 8 9 ________________Iterator with Class Money______________________________ 100 1000 10000 ________________Set Iterator with Class Name___________________________ Amt Bmt Cmt Qmt
定義一個封裝一組物件如何互動的物件。中介透過防止物件顯式地相互引用來促進鬆散耦合,並且它允許您獨立地改變它們的互動方式。
#include <iostream>
#include <string>
#include <list>
class MediatorInterface;
class ColleagueInterface {
std::string name;
public:
ColleagueInterface (const std::string& newName) : name (newName) {}
std::string getName() const {return name;}
virtual void sendMessage (const MediatorInterface&, const std::string&) const = 0;
virtual void receiveMessage (const ColleagueInterface*, const std::string&) const = 0;
};
class Colleague : public ColleagueInterface {
public:
using ColleagueInterface::ColleagueInterface;
virtual void sendMessage (const MediatorInterface&, const std::string&) const override;
private:
virtual void receiveMessage (const ColleagueInterface*, const std::string&) const override;
};
class MediatorInterface {
private:
std::list<ColleagueInterface*> colleagueList;
public:
const std::list<ColleagueInterface*>& getColleagueList() const {return colleagueList;}
virtual void distributeMessage (const ColleagueInterface*, const std::string&) const = 0;
virtual void registerColleague (ColleagueInterface* colleague) {colleagueList.emplace_back (colleague);}
};
class Mediator : public MediatorInterface {
virtual void distributeMessage (const ColleagueInterface*, const std::string&) const override;
};
void Colleague::sendMessage (const MediatorInterface& mediator, const std::string& message) const {
mediator.distributeMessage (this, message);
}
void Colleague::receiveMessage (const ColleagueInterface* sender, const std::string& message) const {
std::cout << getName() << " received the message from " << sender->getName() << ": " << message << std::endl;
}
void Mediator::distributeMessage (const ColleagueInterface* sender, const std::string& message) const {
for (const ColleagueInterface* x : getColleagueList())
if (x != sender) // Do not send the message back to the sender
x->receiveMessage (sender, message);
}
int main() {
Colleague *bob = new Colleague ("Bob"), *sam = new Colleague ("Sam"), *frank = new Colleague ("Frank"), *tom = new Colleague ("Tom");
Colleague* staff[] = {bob, sam, frank, tom};
Mediator mediatorStaff, mediatorSamsBuddies;
for (Colleague* x : staff)
mediatorStaff.registerColleague(x);
bob->sendMessage (mediatorStaff, "I'm quitting this job!");
mediatorSamsBuddies.registerColleague (frank); mediatorSamsBuddies.registerColleague (tom); // Sam's buddies only
sam->sendMessage (mediatorSamsBuddies, "Hooray! He's gone! Let's go for a drink, guys!");
return 0;
}
備忘錄模式在不違反封裝的情況下,捕獲並外部化物件內部狀態,以便稍後將物件恢復到此狀態。 儘管“四人幫”使用友元來實現此模式,但這並不是最佳設計[需要引用]。 它也可以使用PIMPL(指向實現的指標或不透明指標)來實現。 最佳用例是編輯器中的“撤銷 - 重做”。
發起者(要儲存的物件)建立自身快照作為備忘錄物件,並將該引用傳遞給保管者物件。 保管者物件保留備忘錄,直到發起者想要恢復備忘錄物件中記錄的先前狀態。
參見memoize以獲取此模式的老式示例。
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
const std::string NAME = "Object";
template <typename T>
std::string toString (const T& t) {
std::stringstream ss;
ss << t;
return ss.str();
}
class Memento;
class Object {
private:
int value;
std::string name;
double decimal; // and suppose there are loads of other data members
public:
Object (int newValue): value (newValue), name (NAME + toString (value)), decimal ((float)value / 100) {}
void doubleValue() {value = 2 * value; name = NAME + toString (value); decimal = (float)value / 100;}
void increaseByOne() {value++; name = NAME + toString (value); decimal = (float)value / 100;}
int getValue() const {return value;}
std::string getName() const {return name;}
double getDecimal() const {return decimal;}
Memento* createMemento() const;
void reinstateMemento (Memento* mem);
};
class Memento {
private:
Object object;
public:
Memento (const Object& obj): object (obj) {}
Object snapshot() const {return object;} // want a snapshot of Object itself because of its many data members
};
Memento* Object::createMemento() const {
return new Memento (*this);
}
void Object::reinstateMemento (Memento* mem) {
*this = mem->snapshot();
}
class Command {
private:
typedef void (Object::*Action)();
Object* receiver;
Action action;
static std::vector<Command*> commandList;
static std::vector<Memento*> mementoList;
static int numCommands;
static int maxCommands;
public:
Command (Object *newReceiver, Action newAction): receiver (newReceiver), action (newAction) {}
virtual void execute() {
if (mementoList.size() < numCommands + 1)
mementoList.resize (numCommands + 1);
mementoList[numCommands] = receiver->createMemento(); // saves the last value
if (commandList.size() < numCommands + 1)
commandList.resize (numCommands + 1);
commandList[numCommands] = this; // saves the last command
if (numCommands > maxCommands)
maxCommands = numCommands;
numCommands++;
(receiver->*action)();
}
static void undo() {
if (numCommands == 0)
{
std::cout << "There is nothing to undo at this point." << std::endl;
return;
}
commandList[numCommands - 1]->receiver->reinstateMemento (mementoList[numCommands - 1]);
numCommands--;
}
void static redo() {
if (numCommands > maxCommands)
{
std::cout << "There is nothing to redo at this point." << std::endl;
return ;
}
Command* commandRedo = commandList[numCommands];
(commandRedo->receiver->*(commandRedo->action))();
numCommands++;
}
};
std::vector<Command*> Command::commandList;
std::vector<Memento*> Command::mementoList;
int Command::numCommands = 0;
int Command::maxCommands = 0;
int main()
{
int i;
std::cout << "Please enter an integer: ";
std::cin >> i;
Object *object = new Object(i);
Command *commands[3];
commands[1] = new Command(object, &Object::doubleValue);
commands[2] = new Command(object, &Object::increaseByOne);
std::cout << "0.Exit, 1.Double, 2.Increase by one, 3.Undo, 4.Redo: ";
std::cin >> i;
while (i != 0)
{
if (i == 3)
Command::undo();
else if (i == 4)
Command::redo();
else if (i > 0 && i <= 2)
commands[i]->execute();
else
{
std::cout << "Enter a proper choice: ";
std::cin >> i;
continue;
}
std::cout << " " << object->getValue() << " " << object->getName() << " " << object->getDecimal() << std::endl;
std::cout << "0.Exit, 1.Double, 2.Increase by one, 3.Undo, 4.Redo: ";
std::cin >> i;
}
}
觀察者模式定義了物件之間的一對多依賴關係,以便當一個物件改變狀態時,所有依賴它的物件都會被自動通知並更新。
- 問題
- 在應用程式的一個或多個地方,我們需要了解系統事件或應用程式狀態更改。 我們希望有一種標準方法來訂閱監聽系統事件,以及一種標準方法來通知有關方面。 訂閱系統事件或應用程式狀態更改後,通知應該是自動的。 還應該有一種退訂方法。
- 力量
- 觀察者和被觀察者可能應該由物件表示。 觀察者物件將由被觀察者物件通知。
- 解決方案
- 訂閱後,監聽物件將透過方法呼叫方式被通知。
#include <list>
#include <algorithm>
#include <iostream>
using namespace std;
// The Abstract Observer
class ObserverBoardInterface
{
public:
virtual void update(float a,float b,float c) = 0;
};
// Abstract Interface for Displays
class DisplayBoardInterface
{
public:
virtual void show() = 0;
};
// The Abstract Subject
class WeatherDataInterface
{
public:
virtual void registerOb(ObserverBoardInterface* ob) = 0;
virtual void removeOb(ObserverBoardInterface* ob) = 0;
virtual void notifyOb() = 0;
};
// The Concrete Subject
class ParaWeatherData: public WeatherDataInterface
{
public:
void SensorDataChange(float a,float b,float c)
{
m_humidity = a;
m_temperature = b;
m_pressure = c;
notifyOb();
}
void registerOb(ObserverBoardInterface* ob)
{
m_obs.push_back(ob);
}
void removeOb(ObserverBoardInterface* ob)
{
m_obs.remove(ob);
}
protected:
void notifyOb()
{
list<ObserverBoardInterface*>::iterator pos = m_obs.begin();
while (pos != m_obs.end())
{
((ObserverBoardInterface* )(*pos))->update(m_humidity,m_temperature,m_pressure);
(dynamic_cast<DisplayBoardInterface*>(*pos))->show();
++pos;
}
}
private:
float m_humidity;
float m_temperature;
float m_pressure;
list<ObserverBoardInterface* > m_obs;
};
// A Concrete Observer
class CurrentConditionBoard : public ObserverBoardInterface, public DisplayBoardInterface
{
public:
CurrentConditionBoard(ParaWeatherData& a):m_data(a)
{
m_data.registerOb(this);
}
void show()
{
cout<<"_____CurrentConditionBoard_____"<<endl;
cout<<"humidity: "<<m_h<<endl;
cout<<"temperature: "<<m_t<<endl;
cout<<"pressure: "<<m_p<<endl;
cout<<"_______________________________"<<endl;
}
void update(float h, float t, float p)
{
m_h = h;
m_t = t;
m_p = p;
}
private:
float m_h;
float m_t;
float m_p;
ParaWeatherData& m_data;
};
// A Concrete Observer
class StatisticBoard : public ObserverBoardInterface, public DisplayBoardInterface
{
public:
StatisticBoard(ParaWeatherData& a):m_maxt(-1000),m_mint(1000),m_avet(0),m_count(0),m_data(a)
{
m_data.registerOb(this);
}
void show()
{
cout<<"________StatisticBoard_________"<<endl;
cout<<"lowest temperature: "<<m_mint<<endl;
cout<<"highest temperature: "<<m_maxt<<endl;
cout<<"average temperature: "<<m_avet<<endl;
cout<<"_______________________________"<<endl;
}
void update(float h, float t, float p)
{
++m_count;
if (t>m_maxt)
{
m_maxt = t;
}
if (t<m_mint)
{
m_mint = t;
}
m_avet = (m_avet * (m_count-1) + t)/m_count;
}
private:
float m_maxt;
float m_mint;
float m_avet;
int m_count;
ParaWeatherData& m_data;
};
int main(int argc, char *argv[])
{
ParaWeatherData * wdata = new ParaWeatherData;
CurrentConditionBoard* currentB = new CurrentConditionBoard(*wdata);
StatisticBoard* statisticB = new StatisticBoard(*wdata);
wdata->SensorDataChange(10.2, 28.2, 1001);
wdata->SensorDataChange(12, 30.12, 1003);
wdata->SensorDataChange(10.2, 26, 806);
wdata->SensorDataChange(10.3, 35.9, 900);
wdata->removeOb(currentB);
wdata->SensorDataChange(100, 40, 1900);
delete statisticB;
delete currentB;
delete wdata;
return 0;
}
狀態模式允許物件在其內部狀態改變時改變其行為。 該物件將看起來像是改變了它的類。
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <memory>
enum Input {DUCK_DOWN, STAND_UP, JUMP, DIVE};
class Fighter;
class StandingState; class JumpingState; class DivingState;
class FighterState {
public:
static std::shared_ptr<StandingState> standing;
static std::shared_ptr<DivingState> diving;
virtual ~FighterState() = default;
virtual void handleInput (Fighter&, Input) = 0;
virtual void update (Fighter&) = 0;
};
class DuckingState : public FighterState {
private:
int chargingTime;
static const int FullRestTime = 5;
public:
DuckingState() : chargingTime(0) {}
virtual void handleInput (Fighter&, Input) override;
virtual void update (Fighter&) override;
};
class StandingState : public FighterState {
public:
virtual void handleInput (Fighter&, Input) override;
virtual void update (Fighter&) override;
};
class JumpingState : public FighterState {
private:
int jumpingHeight;
public:
JumpingState() {jumpingHeight = std::rand() % 5 + 1;}
virtual void handleInput (Fighter&, Input) override;
virtual void update (Fighter&) override;
};
class DivingState : public FighterState {
public:
virtual void handleInput (Fighter&, Input) override;
virtual void update (Fighter&) override;
};
std::shared_ptr<StandingState> FighterState::standing (new StandingState);
std::shared_ptr<DivingState> FighterState::diving (new DivingState);
class Fighter {
private:
std::string name;
std::shared_ptr<FighterState> state;
int fatigueLevel = std::rand() % 10;
public:
Fighter (const std::string& newName) : name (newName), state (FighterState::standing) {}
std::string getName() const {return name;}
int getFatigueLevel() const {return fatigueLevel;}
virtual void handleInput (Input input) {state->handleInput (*this, input);} // delegate input handling to 'state'.
void changeState (std::shared_ptr<FighterState> newState) {state = newState; updateWithNewState();}
void standsUp() {std::cout << getName() << " stands up." << std::endl;}
void ducksDown() {std::cout << getName() << " ducks down." << std::endl;}
void jumps() {std::cout << getName() << " jumps into the air." << std::endl;}
void dives() {std::cout << getName() << " makes a dive attack in the middle of the jump!" << std::endl;}
void feelsStrong() {std::cout << getName() << " feels strong!" << std::endl;}
void changeFatigueLevelBy (int change) {fatigueLevel += change; std::cout << "fatigueLevel = " << fatigueLevel << std::endl;}
private:
virtual void updateWithNewState() {state->update(*this);} // delegate updating to 'state'
};
void StandingState::handleInput (Fighter& fighter, Input input) {
switch (input) {
case STAND_UP: std::cout << fighter.getName() << " remains standing." << std::endl; return;
case DUCK_DOWN: fighter.changeState (std::shared_ptr<DuckingState> (new DuckingState)); return fighter.ducksDown();
case JUMP: fighter.jumps(); return fighter.changeState (std::shared_ptr<JumpingState> (new JumpingState));
default: std::cout << "One cannot do that while standing. " << fighter.getName() << " remains standing by default." << std::endl;
}
}
void StandingState::update (Fighter& fighter) {
if (fighter.getFatigueLevel() > 0)
fighter.changeFatigueLevelBy(-1);
}
void DuckingState::handleInput (Fighter& fighter, Input input) {
switch (input) {
case STAND_UP: fighter.changeState (FighterState::standing); return fighter.standsUp();
case DUCK_DOWN:
std::cout << fighter.getName() << " remains in ducking position, ";
if (chargingTime < FullRestTime) std::cout << "recovering in the meantime." << std::endl;
else std::cout << "fully recovered." << std::endl;
return update (fighter);
default:
std::cout << "One cannot do that while ducking. " << fighter.getName() << " remains in ducking position by default." << std::endl;
update (fighter);
}
}
void DuckingState::update (Fighter& fighter) {
chargingTime++;
std::cout << "Charging time = " << chargingTime << "." << std::endl;
if (fighter.getFatigueLevel() > 0)
fighter.changeFatigueLevelBy(-1);
if (chargingTime >= FullRestTime && fighter.getFatigueLevel() <= 3)
fighter.feelsStrong();
}
void JumpingState::handleInput (Fighter& fighter, Input input) {
switch (input) {
case DIVE: fighter.changeState (FighterState::diving); return fighter.dives();
default:
std::cout << "One cannot do that in the middle of a jump. " << fighter.getName() << " lands from his jump and is now standing again." << std::endl;
fighter.changeState (FighterState::standing);
}
}
void JumpingState::update (Fighter& fighter) {
std::cout << fighter.getName() << " has jumped " << jumpingHeight << " feet into the air." << std::endl;
if (jumpingHeight >= 3)
fighter.changeFatigueLevelBy(1);
}
void DivingState::handleInput (Fighter& fighter, Input) {
std::cout << "Regardless of what the user input is, " << fighter.getName() << " lands from his dive and is now standing again." << std::endl;
fighter.changeState (FighterState::standing);
}
void DivingState::update (Fighter& fighter) {
fighter.changeFatigueLevelBy(2);
}
int main() {
std::srand(std::time(nullptr));
Fighter rex ("Rex the Fighter"), borg ("Borg the Fighter");
std::cout << rex.getName() << " and " << borg.getName() << " are currently standing." << std::endl;
int choice;
auto chooseAction = [&choice](Fighter& fighter) {
std::cout << std::endl << DUCK_DOWN + 1 << ") Duck down " << STAND_UP + 1 << ") Stand up " << JUMP + 1
<< ") Jump " << DIVE + 1 << ") Dive in the middle of a jump" << std::endl;
std::cout << "Choice for " << fighter.getName() << "? ";
std::cin >> choice;
const Input input1 = static_cast<Input>(choice - 1);
fighter.handleInput (input1);
};
while (true) {
chooseAction (rex);
chooseAction (borg);
}
}
定義一系列演算法,封裝每個演算法,並使它們可互換。 策略允許演算法獨立於使用它的客戶端而變化。
#include <iostream>
using namespace std;
class StrategyInterface
{
public:
virtual void execute() const = 0;
};
class ConcreteStrategyA: public StrategyInterface
{
public:
void execute() const override
{
cout << "Called ConcreteStrategyA execute method" << endl;
}
};
class ConcreteStrategyB: public StrategyInterface
{
public:
void execute() const override
{
cout << "Called ConcreteStrategyB execute method" << endl;
}
};
class ConcreteStrategyC: public StrategyInterface
{
public:
void execute() const override
{
cout << "Called ConcreteStrategyC execute method" << endl;
}
};
class Context
{
private:
StrategyInterface * strategy_;
public:
explicit Context(StrategyInterface *strategy):strategy_(strategy)
{
}
void set_strategy(StrategyInterface *strategy)
{
strategy_ = strategy;
}
void execute() const
{
strategy_->execute();
}
};
int main(int argc, char *argv[])
{
ConcreteStrategyA concreteStrategyA;
ConcreteStrategyB concreteStrategyB;
ConcreteStrategyC concreteStrategyC;
Context contextA(&concreteStrategyA);
Context contextB(&concreteStrategyB);
Context contextC(&concreteStrategyC);
contextA.execute(); // output: "Called ConcreteStrategyA execute method"
contextB.execute(); // output: "Called ConcreteStrategyB execute method"
contextC.execute(); // output: "Called ConcreteStrategyC execute method"
contextA.set_strategy(&concreteStrategyB);
contextA.execute(); // output: "Called ConcreteStrategyB execute method"
contextA.set_strategy(&concreteStrategyC);
contextA.execute(); // output: "Called ConcreteStrategyC execute method"
return 0;
}
透過在操作中定義演算法的骨架,並將某些步驟推遲到子類,模板方法允許子類重新定義該演算法的某些步驟,而不改變演算法的結構。
#include <ctime>
#include <assert.h>
#include <iostream>
namespace wikibooks_design_patterns
{
/**
* An abstract class that is common to several games in
* which players play against the others, but only one is
* playing at a given time.
*/
class Game
{
public:
Game(): playersCount(0), movesCount(0), playerWon(-1)
{
srand( (unsigned)time( NULL));
}
/* A template method : */
void playOneGame(const int playersCount = 0)
{
if (playersCount)
{
this->playersCount = playersCount;
}
InitializeGame();
assert(this->playersCount);
int j = 0;
while (!endOfGame())
{
makePlay(j);
j = (j + 1) % this->playersCount;
if (!j)
{
++movesCount;
}
}
printWinner();
}
protected:
virtual void initializeGame() = 0;
virtual void makePlay(int player) = 0;
virtual bool endOfGame() = 0;
virtual void printWinner() = 0;
private:
void InitializeGame()
{
movesCount = 0;
playerWon = -1;
initializeGame();
}
protected:
int playersCount;
int movesCount;
int playerWon;
};
//Now we can extend this class in order
//to implement actual games:
class Monopoly: public Game {
/* Implementation of necessary concrete methods */
void initializeGame() {
// Initialize players
playersCount = rand() * 7 / RAND_MAX + 2;
// Initialize money
}
void makePlay(int player) {
// Process one turn of player
// Decide winner
if (movesCount < 20)
return;
const int chances = (movesCount > 199) ? 199 : movesCount;
const int random = MOVES_WIN_CORRECTION * rand() * 200 / RAND_MAX;
if (random < chances)
playerWon = player;
}
bool endOfGame() {
// Return true if game is over
// according to Monopoly rules
return (-1 != playerWon);
}
void printWinner() {
assert(playerWon >= 0);
assert(playerWon < playersCount);
// Display who won
std::cout<<"Monopoly, player "<<playerWon<<" won in "<<movesCount<<" moves."<<std::endl;
}
private:
enum
{
MOVES_WIN_CORRECTION = 20,
};
};
class Chess: public Game {
/* Implementation of necessary concrete methods */
void initializeGame() {
// Initialize players
playersCount = 2;
// Put the pieces on the board
}
void makePlay(int player) {
assert(player < playersCount);
// Process a turn for the player
// decide winner
if (movesCount < 2)
return;
const int chances = (movesCount > 99) ? 99 : movesCount;
const int random = MOVES_WIN_CORRECTION * rand() * 100 / RAND_MAX;
//std::cout<<random<<" : "<<chances<<std::endl;
if (random < chances)
playerWon = player;
}
bool endOfGame() {
// Return true if in Checkmate or
// Stalemate has been reached
return (-1 != playerWon);
}
void printWinner() {
assert(playerWon >= 0);
assert(playerWon < playersCount);
// Display the winning player
std::cout<<"Player "<<playerWon<<" won in "<<movesCount<<" moves."<<std::endl;
}
private:
enum
{
MOVES_WIN_CORRECTION = 7,
};
};
}
int main()
{
using namespace wikibooks_design_patterns;
Game* game = NULL;
Chess chess;
game = &chess;
for (unsigned i = 0; i < 100; ++i)
game->playOneGame();
Monopoly monopoly;
game = &monopoly;
for (unsigned i = 0; i < 100; ++i)
game->playOneGame();
return 0;
}
訪問者模式將表示要對物件結構的元素執行的操作,透過允許您定義新的操作,而不改變其操作的元素的類。
#include <string>
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Wheel;
class Engine;
class Body;
class Car;
// interface to all car 'parts'
struct CarElementVisitor
{
virtual void visit(Wheel& wheel) const = 0;
virtual void visit(Engine& engine) const = 0;
virtual void visit(Body& body) const = 0;
virtual void visitCar(Car& car) const = 0;
};
// interface to one part
struct CarElement
{
virtual void accept(const CarElementVisitor& visitor) = 0;
};
// wheel element, there are four wheels with unique names
class Wheel : public CarElement
{
public:
explicit Wheel(const string& name) : name_(name){}
const string& getName() const
{
return name_;
}
void accept(const CarElementVisitor& visitor)
{
visitor.visit(*this);
}
private:
string name_;
};
class Engine : public CarElement
{
public:
void accept(const CarElementVisitor& visitor)
{
visitor.visit(*this);
}
};
class Body : public CarElement
{
public:
void accept(const CarElementVisitor& visitor)
{
visitor.visit(*this);
}
};
class Car
{
public:
vector<unique_ptr<CarElement>>& getElements()
{
return elements_;
}
Car() {
// assume that neither push_back nor Wheel(const string&) may throw
elements_.push_back( make_unique<Wheel>("front left") );
elements_.push_back( make_unique<Wheel>("front right") );
elements_.push_back( make_unique<Wheel>("back left") );
elements_.push_back( make_unique<Wheel>("back right") );
elements_.push_back( make_unique<Body>() );
elements_.push_back( make_unique<Engine>() );
}
private:
vector<unique_ptr<CarElement>> elements_;
};
// PrintVisitor and DoVisitor show by using a different implementation the Car class is unchanged even though the algorithm is different in PrintVisitor and DoVisitor.
class CarElementPrintVisitor : public CarElementVisitor
{
public:
void visit(Wheel& wheel) const
{
cout << "Visiting " << wheel.getName() << " wheel" << endl;
}
void visit(Engine& engine) const
{
cout << "Visiting engine" << endl;
}
void visit(Body& body) const
{
cout << "Visiting body" << endl;
}
void visitCar(Car& car) const
{
cout << endl << "Visiting car" << endl;
vector<unique_ptr<CarElement>>& elems = car.getElements();
for(auto &it : elems)
{
// this issues the callback i.e. to this from the element
it->accept(*this);
}
cout << "Visited car" << endl;
}
};
class CarElementDoVisitor : public CarElementVisitor
{
public:
// these are specific implementations added to the original object without modifying the original struct
void visit(Wheel& wheel) const
{
cout << "Kicking my " << wheel.getName() << " wheel" << endl;
}
void visit(Engine& engine) const
{
cout << "Starting my engine" << endl;
}
void visit(Body& body) const
{
cout << "Moving my body" << endl;
}
void visitCar(Car& car) const
{
cout << endl << "Starting my car" << endl;
vector<unique_ptr<CarElement>>& elems = car.getElements();
for(auto& it : elems)
{
it->accept(*this); // this issues the callback i.e. to this from the element
}
cout << "Stopped car" << endl;
}
};
int main()
{
Car car;
CarElementPrintVisitor printVisitor;
CarElementDoVisitor doVisitor;
printVisitor.visitCar(car);
doVisitor.visitCar(car);
return 0;
}
一種模式,經常被需要維護同一資料的多個檢視的應用程式使用。 模型 - 檢視 - 控制器模式直到最近[需要引用] 是一種非常常見的模式,尤其是針對圖形使用者介面程式設計,它將程式碼分成三部分。 模型、檢視和控制器。
模型是實際的資料表示(例如,陣列與連結串列)或其他表示資料庫的物件。 檢視是讀取模型的介面或胖客戶端 GUI。 控制器提供了更改或修改資料的介面,然後選擇“下一個最佳檢視”(NBV)。
新手可能會將這種“MVC”模型視為浪費,主要是因為您在執行時使用許多額外的物件,而一個大型物件似乎就足夠了。 但是 MVC 模式的秘密不在於編寫程式碼,而在於維護它,並允許人們修改程式碼而不改變其他太多東西。 此外,請記住,不同的開發人員有不同的優勢和劣勢,因此圍繞 MVC 建立團隊更容易。 想象一下一個負責精美檢視的檢視團隊,一個瞭解大量資料的模型團隊,以及一個瞭解應用程式流程全域性、處理請求、與模型合作以及為該客戶端選擇最合適的下一個檢視的控制器團隊。
例如:一個簡單的中心資料庫可以使用一個“模型”進行組織,例如,一個簡單的陣列。 但是,稍後,使用連結串列可能更適用。 所有陣列訪問都必須重新制作為其各自的連結串列形式(例如,您將 myarray[5] 更改為 mylist.at(5) 或您使用的語言中的任何等效項)。
好吧,如果我們遵循 MVC 模式,中心資料庫將使用某種函式訪問,例如 myarray.at(5)。 如果我們將模型從陣列更改為連結串列,我們只需要用模型更改檢視,整個程式就會改變。 保持介面相同,但更改其基礎。 這將允許我們比以前更自由、更快地進行最佳化。
模型 - 檢視 - 控制器模式的一個主要優點顯然是能夠在實現不同的檢視時重用應用程式的邏輯(在模型中實現)。 在 Web 開發中發現了一個很好的例子,其中一個常見任務是在現有的軟體部分中實現外部 API。 如果已經乾淨地遵循了 MVC 模式,這隻需要修改控制器,控制器可以根據使用者代理請求的內容型別渲染不同型別的檢視。
庫允許在程式中重用現有程式碼。 庫類似於程式,不同之處在於它們不依賴於main()來完成工作,而是呼叫庫提供的特定函式來完成工作。 函式提供了正在編寫的程式與正在使用的庫之間的介面。 此介面稱為應用程式程式設計介面或 API。
庫應該並且往往是特定於領域的,以便允許在應用程式之間具有更大的移動性,並提供擴充套件的專業化。 不是特定於領域的庫通常是僅標頭檔案分發,用於靜態連結,以便編譯器和應用程式僅使用所需的程式碼部分。
- 什麼是 API?
對於程式設計師來說,作業系統由其API定義。 API代表應用程式程式設計介面。 API包含應用程式程式可以與硬體或作業系統(或提供一組介面給程式設計師的任何其他應用程式(即:庫))進行通訊的所有函式呼叫,以及相關資料型別和結構的定義。 大多數API在應用程式軟體開發工具包(SDK)中定義,用於程式開發。
簡單來說,API可以看作是使用者(或使用者程式)與作業系統、硬體或其他程式進行互動以使它們執行任務(這可能還會導致獲得結果訊息)的介面。
- API 可以稱為框架嗎?
不,框架可以提供 API,但框架不僅僅是簡單的 API。 預設情況下,框架還定義了程式碼的編寫方式,它是一組解決方案(甚至包括類),作為一個整體解決了對一組有限的關聯問題的處理,並提供了 API 和預設功能,設計良好的框架允許其與類似的框架互換,努力提供相同的 API。
如檔案組織部分所示,編譯庫由預處理器包含的 C++ 標頭檔案和連結器用來生成最終編譯的二進位制庫檔案組成。 對於動態連結庫,只有載入程式碼被新增到使用它們的編譯中,庫的實際載入在執行時在記憶體中完成。
程式可以使用兩種形式的庫:靜態庫或動態庫,這取決於程式設計師如何決定分發其程式碼,甚至取決於第三方庫使用的許可證。本手冊中關於靜態庫和動態庫的部分將深入探討這個主題。
第三方庫
[edit | edit source]除了標準庫(例如垃圾回收)之外的附加功能,可以透過第三方庫獲得(通常是免費的),但請記住,第三方庫不一定提供與標準庫一樣普遍的跨平臺功能或與標準庫一致的 API 風格。它們存在的首要目的是避免重複造輪子,並使努力集中起來;幾代程式設計師花費了大量的精力來編寫安全且“可移植”的程式碼。
程式設計師應該瞭解一些庫,或者至少對它們是什麼有一個大概的瞭解。隨著時間的推移,一致性和擴充套件的參考將使一些庫從其他庫中脫穎而出。一個值得注意的例子是廣受讚譽的Boost 庫集合,我們將在後面對其進行考察。
- 第三方庫的許可證
程式設計師也可能受到他無法直接控制的外部庫所使用的許可證要求的限制。例如,不允許在閉源應用程式中使用GNU 通用公共許可證 (GNU GPL) 程式碼,為了解決這個問題,自由軟體基金會提供了 GNU LGPL 許可證的替代方案,該許可證允許這種使用,但僅限於動態連結形式。這反映了許多其他法律要求,程式設計師必須遵守這些法律要求。
庫有兩種形式:原始碼形式或編譯/二進位制形式。原始碼形式的庫必須先編譯,然後才能包含到另一個專案中。這會將庫的 cpp 檔案轉換為 lib 檔案。如果程式必須重新編譯才能使用新版本的庫執行,但不需要進行任何其他更改,則該庫被稱為原始碼相容。如果程式不需要修改和重新編譯就可以使用新版本的庫,則該庫被稱為二進位制相容。
靜態庫和動態庫
[edit | edit source]
使用靜態二進位制檔案的優點
- 簡化程式分發(檔案更少)。
- 程式碼簡化(不需要動態庫中所需的版本檢查)。
- 只編譯使用的程式碼。
使用靜態二進位制檔案的缺點
- 資源浪費:生成更大的二進位制檔案,因為庫被編譯到可執行檔案中。浪費記憶體,因為庫不能在程序之間共享(在記憶體中)(取決於作業系統)。
- 程式不會從庫中的錯誤修復或擴充套件中獲益,除非重新編譯。
- 庫的二進位制/原始碼相容性
如果動態連結到該庫的早期版本的程式能夠繼續使用同一庫的另一個版本執行,則該庫被稱為二進位制相容。如果程式需要重新編譯才能與每個新版本的庫一起執行,則該庫被稱為原始碼相容。
生成二進位制相容庫有利於分發,但程式設計師更難維護。如果庫只有原始碼相容,通常最好進行靜態連結,因為它不會給終端使用者帶來問題。
二進位制相容性節省了大量麻煩,並表明該庫已達到穩定狀態。它使為特定平臺分發軟體變得更容易。如果不確保版本之間的二進位制相容性,人們將被迫提供靜態連結的二進位制檔案。
- 僅標頭檔案庫
另一個關於庫的常見區別是它們是如何分發的(關於結構和使用)。一個僅包含在標頭檔案中的庫被稱為僅標頭檔案庫。這通常意味著它們更簡單且易於使用,但是對於複雜的程式碼來說,這不是理想的解決方案。它不僅會影響可讀性,還會導致編譯時間更長。此外,由於生成的內聯,編譯器及其最佳化能力(或選項)可能會生成更大的二進位制檔案。這在主要使用模板實現的庫中可能並不那麼重要。僅標頭檔案庫始終包含實現的原始碼,商業庫很少。
示例:配置 MS Visual C++ 以使用外部庫
[edit | edit source]Boost 庫被用作示例庫。
假設您已經解壓縮並構建了 Boost 庫的二進位制部分。以下是要執行的步驟。
包含目錄
[edit | edit source]設定包含目錄。它包含標頭檔案(.h/hpp),這些檔案描述庫介面。
庫目錄
[edit | edit source]設定庫目錄。它包含預編譯的庫檔案(.lib)。
庫檔案
[edit | edit source]在要使用的庫的附加依賴項中輸入庫檔名。
一些庫(例如 Boost)使用自動連結來自動選擇用於連結的庫檔案,具體取決於包含的標頭檔案。如果您的編譯器支援自動連結,則無需手動選擇這些庫的檔名。
動態庫
[edit | edit source]對於動態載入(.dll)庫,還必須將 DLL 檔案放在與可執行檔案相同的資料夾中,或者放在系統路徑中。
執行時庫
[edit | edit source]庫還必須使用與專案中使用的相同的執行時庫進行編譯。因此,許多庫都有不同的版本,具體取決於它們是為單執行緒或多執行緒執行時以及除錯或釋出執行時編譯的,以及它們是否包含除錯符號。
Boost 專案(http://www.boost.org/)提供免費的 同行評審、開源 庫,用於擴充套件 C++ 的功能。大多數庫都是根據 Boost 軟體許可證 授權的,該許可證旨在允許 Boost 用於開源和 閉源 專案。
Boost 的許多創始人都在 C++ 標準 委員會,並且幾個 Boost 庫已被接受並納入 技術報告 1 中的 C++0x。雖然 Boost 最初是由 C++ 標準委員會庫工作組的成員開始的,但參與者已擴充套件到包括來自 C++ 社群的數千名程式設計師。
重點是與 C++ 標準庫配合良好的庫。這些庫面向各種 C++ 使用者和應用程式領域,並且被數千名程式設計師經常使用。它們從通用的庫(如 SmartPtr)到 OS 抽象(如 FileSystem),再到主要面向其他庫開發人員和高階 C++ 使用者的庫(如 MPL)。
另一個目標是建立“現有實踐”並提供參考實現,以便 Boost 庫適合最終標準化。十個 Boost 庫將被納入 C++ 標準委員會 的即將釋出的 C++ 標準庫技術報告 中,作為成為未來 C++ 標準的一部分的步驟。
為了確保效率和靈活性,Boost 廣泛使用了 模板。Boost 一直是 C++ 中 泛型程式設計 和 超程式設計 的廣泛工作和研究的來源。
庫
[edit | edit source]- 併發程式設計
- ... TODO
- 演算法
- 併發程式設計(執行緒)
- 容器
- array - 使用 STL 容器語義管理固定大小的陣列
- Boost 圖形庫 (BGL) - 泛型圖形容器、元件和演算法
- multi-array - 簡化 N 維陣列的建立
- 多索引容器 - 帶有內建索引的容器,允許不同的排序和訪問語義
- 指標容器 - 模仿大多數標準 STL 容器的容器,允許透明地管理指向值的指標
- 屬性對映 - 以概念形式提供的介面規範和用於將鍵值對映到物件的通用介面
- variant - 一種安全的泛型基於堆疊的物件容器,允許高效地儲存和訪問型別為可從編譯時指定的型別集中選擇的型別,並且必須在編譯時指定。
- 正確性和 測試
- 資料結構
- dynamic_bitset - 動態
std::bitset-類似資料結構
- dynamic_bitset - 動態
- 函式物件和 高階程式設計
- bind 和 mem_fn - 用於函式、函式物件、函式指標和成員函式的通用繫結器
- function - 用於延遲呼叫的函式物件包裝器。此外,還提供了一種用於回撥的通用機制
- functional - 對 C++ 標準庫中指定的函式物件介面卡進行了增強,包括
- hash - C++ 技術報告 1 (TR1) 中指定的雜湊函式物件的實現。可以用作無序關聯容器的預設雜湊函式
- lambda - 本著 lambda 抽象 的精神,允許在呼叫站點定義小的匿名函式物件和對這些物件的運算,使用佔位符,特別是用於從演算法中延遲迴調。
- ref - 提供實用程式類模板,以增強標準 C++ 引用功能,特別是用於泛型函式
- result_of - 有助於確定呼叫表示式的型別
- Signals2 - 管理的訊號和槽回撥實現
- 泛型程式設計
- 圖形
- 輸入/輸出
- 跨語言支援(適用於 Python)
- 迭代器
- 數學和數值
- 記憶體
- pool - 提供了一種簡單的基於隔離儲存的記憶體管理方案
- smart_ptr - 一組具有不同指向物件管理語義的智慧指標類模板
- scoped_ptr - 擁有指向物件(單個物件)
- scoped_array - 類似於 scoped_ptr,但用於陣列
- shared_ptr - 可能與其他 shared_ptr 共享指標。當最後一個指向它的 shared_ptr 被銷燬時,指向物件被銷燬
- shared_array - 類似於 shared_ptr,但用於陣列
- weak_ptr - 提供對由 shared_ptr 管理的物件的“弱”引用
- intrusive_ptr - 類似於 shared_ptr,但使用指向物件提供的引用計數
- utility - 各種支援類,包括
- 從成員派生基本類 - 為需要在其自身(即派生類)建構函式的初始化列表中初始化基類成員的類提供了一種解決方法
- checked delete - 檢查是否嘗試使用指向不完整型別的指標銷燬物件或物件陣列
- next 和 prior 函式 - 允許更輕鬆地移動正向或雙向迭代器,特別是當此類移動的結果需要儲存在單獨的迭代器中時(即不應更改原始迭代器)
- noncopyable - 允許禁止複製構造和複製賦值
- addressof - 允許獲取物件的實際地址,在此過程中繞過 operator&() 的任何過載
- result_of - 有助於確定呼叫表示式的型別
- 其他
- 解析器
- 預處理器超程式設計
- 字串 和文字處理
- 模板超程式設計
- 針對有問題的編譯器的變通方法
當前的 Boost 版本包含 87 個獨立的庫,包括以下三個
該boost::noncopyable實用程式類 確保類物件永遠不會被複制.
class C : boost::noncopyable
{
...
};
Boost 包含 uBLAS 線性代數 庫(更快的替代庫包括 armadillo 和 eigen),具有對向量和矩陣的 BLAS 支援。uBlas 支援各種線性代數運算,並且與一些廣泛使用的數值庫(如 ATLAS、BLAS 和 LAPACK)繫結。
- 示例展示瞭如何將向量與矩陣相乘
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <iostream>
using namespace boost::numeric::ublas;
/* "y = Ax" example */
int main ()
{
vector<double> x(2);
x(0) = 1; x(1) = 2;
matrix<double> A(2,2);
A(0,0) = 0; A(0,1) = 1;
A(1,0) = 2; A(1,1) = 3;
vector<double> y = prod(A, x);
std::cout << y << std::endl;
return 0;
}
Boost 提供與分佈無關的 偽隨機數生成器 和與 PRNG 無關的機率分佈,這些分佈組合在一起構建一個具體的生成器。
#include <boost/random.hpp>
#include <ctime>
using namespace boost;
double SampleNormal (double mean, double sigma)
{
// Create a Mersenne twister random number generator
// that is seeded once with #seconds since 1970
static mt19937 rng(static_cast<unsigned> (std::time(0)));
// select Gaussian probability distribution
normal_distribution<double> norm_dist(mean, sigma);
// bind random number generator to distribution, forming a function
variate_generator<mt19937&, normal_distribution<double> > normal_sampler(rng, norm_dist);
// sample from the distribution
return normal_sampler();
}
有關更多詳細資訊,請參見 Boost 隨機數庫。
演示建立執行緒的示例程式碼
#include <boost/thread/thread.hpp>
#include <iostream>
using namespace std;
void hello_world()
{
cout << "Hello world, I'm a thread!" << endl;
}
int main(int argc, char* argv[])
{
// start two new threads that calls the "hello_world" function
boost::thread my_thread1(&hello_world);
boost::thread my_thread2(&hello_world);
// wait for both threads to finish
my_thread1.join();
my_thread2.join();
return 0;
}
另請參見 使用 Boost 進行執行緒處理 - 第一部:建立執行緒
使用互斥鎖來強制對函式進行排他訪問的示例用法
#include <iostream>
#include <boost/thread.hpp>
void locked_function ()
{
// function access mutex
static boost::mutex m;
// wait for mutex lock
boost::mutex::scoped_lock lock(m);
// critical section
// TODO: Do something
// auto-unlock on return
}
int main (int argc, char* argv[])
{
locked_function();
return 0;
}
屬性的讀/寫鎖定的示例
#include <iostream>
#include <boost/thread.hpp>
/** General class for thread-safe properties of any type. */
template <class T>
class lock_prop : boost::noncopyable {
public:
lock_prop () {}
/** Set property value. */
void operator = (const T & v) {
// wait for exclusive write access
boost::unique_lock<boost::shared_mutex> lock(mutex);
value = v;
}
/** Get property value. */
T operator () () const {
// wait for shared read access
boost::shared_lock<boost::shared_mutex> lock(mutex);
return value;
}
private:
/// Property value.
T value;
/// Mutex to restrict access
mutable boost::shared_mutex mutex;
};
int main () {
// read/write locking property
lock_prop<int> p1;
p1 = 10;
int a = p1();
return 0;
}
- Boost.Threads 簡介 在 Dr. Dobb's Journal 中。(2002 年)
- Boost Threads 中的新功能? 在 Dr. Dobb's Journal 中。(2008 年)
- Boost.Threads API 參考.
- 執行緒池庫 基於 Boost.Thread
- Ceemple Ceemple 中包含 Boost,Ceemple 是一個基於 JIT 的 C++ 快速技術計算環境。
本節旨在向程式設計師介紹跨多個作業系統環境的可移植性程式設計。在當今世界,將應用程式限制在單個作業系統或計算機平臺似乎不合適,並且越來越需要以跨平臺方式進行程式設計。
Win32 API 是在Windows 作業系統中定義的一組函式,換句話說,它是Windows API,這是Microsoft 對在Microsoft Windows 作業系統 中可用的核心 應用程式程式設計介面 集的名稱。它專為 C/C++ 程式使用而設計,是軟體應用程式 與Windows 系統進行互動的最直接方式。當前版本的Windows 中,Windows 驅動程式模型 提供了對Windows 系統的更低階訪問,主要用於裝置驅動程式。
可以使用 MSDN 庫(http://msdn.microsoft.com/)從Microsoft 本身獲得有關API 和支援的更多資訊,這實質上是面向使用 Microsoft 工具、產品和技術的開發人員的資源。它包含大量技術程式設計資訊,包括示例程式碼、文件、技術文章和參考指南。您還可以檢視 Wikibooks Windows 程式設計 書籍,以獲取有關超出本書範圍的一些更詳細的資訊。
Windows 的 軟體開發工具包 (SDK) 可供使用,它提供文件和工具,使開發人員能夠使用Windows API 和相關Windows 技術建立軟體。(http://www.microsoft.com/downloads/)
- 歷史
Windows API 一直向程式設計師公開構建它的各種 Windows 系統的基礎結構的大部分。這有利於為 Windows 程式設計師提供了極大的靈活性和對應用程式的控制權。但是,這也使 Windows 應用程式在處理與 圖形使用者介面 相關的各種低階(有時很繁瑣)操作方面承擔了很大責任。
Charles Petzold(撰寫了各種廣為閱讀的 Windows API 著作的作者)說:“Windows 1.0 SDK 中的原始 hello-world 程式有點聳人聽聞。HELLO.C 約有 150 行程式碼,HELLO.RC 資源指令碼還有另外 20 行左右。(...) 老練的 C 程式設計師在遇到 Windows hello-world 程式時通常會驚恐或發笑。”. hello world 程式 是一個經常使用的程式設計示例,通常旨在展示在系統上能夠實際執行某些操作(即,列印一行顯示“Hello World”的程式碼)的最簡單應用程式。
多年來,Windows 作業系統進行了各種更改和新增,Windows API 也隨之更改和擴充套件。Windows 1.0 的 windows API 支援不到 450 個函式呼叫,而現代版本的 Windows API 則有數千個。總的來說,介面一直保持相當一致,但是,舊的 Windows 1.0 應用程式對於習慣使用現代 Windows API 的程式設計師來說仍然看起來很熟悉。
Microsoft 非常重視維護軟體的向後相容性。為了實現這一點,Microsoft 有時甚至會支援以未記錄或(在程式設計上)非法的方式使用 API 的軟體。Raymond Chen(一位在 Windows API 上工作的 Microsoft 開發人員)說,他“可能可以連續幾個月只寫關於應用程式的錯誤行為以及我們必須採取什麼措施才能使它們再次執行(通常是儘管有自身錯誤)。這就是為什麼當人們指責 Microsoft 在作業系統升級過程中惡意破壞應用程式時,我特別生氣。如果任何應用程式在 Windows 95 上無法執行,我將其視為個人失誤。”
Win32 使用擴充套件的資料型別集,使用 C 的 typedef 機制,包括
- BYTE -
無符號8 位整數。 - DWORD - 32 位
無符號整數。 - LONG - 32 位帶符號整數。
- LPDWORD - 指向 DWORD 的 32 位指標。
- LPCSTR - 指向常量字元字串的 32 位指標。
- LPSTR - 指向字元字串的 32 位指標。
- UINT - 32 位
無符號int。 - WORD - 16 位
無符號int。 - HANDLE - 指向系統資料的非透明指標。
當然,使用Win32 API 進行程式設計時,標準資料型別也可使用。
在 Windows 中,庫程式碼以多種形式存在,並且可以透過多種方式訪問。
通常,唯一需要的是在原始碼中包含相應的標頭檔案,以便向編譯器提供資訊,並且在連結階段會連結到 .lib 檔案。
此 .lib 檔案包含要靜態連結到已編譯的目的碼的程式碼,或者包含允許訪問系統上動態連結到二進位制庫 (.DLL) 的程式碼。
也可以在 C++ 中透過包含適當的資訊(如編譯和連結時的匯入/匯出表)來生成二進位制庫 .DLL。
DLL 代表動態連結庫,是某些程式中使用的基本函式檔案。許多較新的 C++ IDE(如 Dev-CPP)支援此類庫。
Windows 上的常用庫包括平臺軟體開發工具包、Microsoft Foundation Class 和 .Net Framework 程式集的 C++ 介面提供的庫。
儘管嚴格意義上不作為庫程式碼使用,但平臺 SDK 和其他庫提供了一組標準化的介面,用於訪問透過 元件物件模型 實現的 Windows 物件。
時間測量必須來自 OS,因為它與執行的硬體相關,不幸的是,大多數計算機沒有標準的高精度、高精度的時鐘,而且訪問速度很快。
MSDN 時間函式(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/time_functions.asp)
計時器函式效能(http://developer.nvidia.com/object/timer_function_performance.html)
GetTickCount 的精度(取決於您的計時器滴答速率)為 1 毫秒,其準確度通常在預期誤差為 10-55 毫秒的範圍內,最好的情況是它以恆定速率遞增。(WaitForSingleObject 使用相同的計時器)。
GetSystemTimeAsFileTime 的精度為 100 納秒,其準確度與 GetTickCount 相似。
QueryPerformanceCounter 獲取速度可能較慢,但準確度更高,使用 HAL(在 ACPI 的幫助下),一個問題是,由於 LSB 上的垃圾,它可能會在超頻的 PC 上倒退,請注意,除非提供的 LARGE_INTEGER 經過 DWORD 對齊,否則這些函式會失敗。
效能計數器值可能意外向前跳躍(http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323&)
timeGetTime(透過 winmm.dll)的精度約為 5 毫秒。
MakeSureDirectoryPathExists(透過影像幫助庫 - IMAGHLP.DLL,#pragma comment( lib, "imagehlp.lib" ), #include <imagehlp.h>) 建立目錄,僅用於建立/強制給定目錄樹或多個目錄的存在,或者如果連結已經存在,請注意它是單執行緒的。
網路應用程式通常在 Windows 上使用 WinSock API 函式使用 C++ 構建。
資原始檔可能是 WIN32 API 中最有用元素之一,它們是我們編寫選單、新增圖示、背景、音樂以及為我們的程式新增更多美觀元素的方式。遺憾的是,在編譯中使用資原始檔的做法是今天那些使用 MS Visual Studio IDE(資源編輯器、資源結構理解)的人所使用的。
資源在 .rc 檔案(資源 c)中定義,並在編譯的連結階段包含。資原始檔與標頭檔案(通常稱為 resource.h)緊密配合,該標頭檔案包含每個 ID 的定義。
例如,一個簡單的 RC 檔案可能包含一個選單
//////////////
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&About", ID_FILE_ABOUT
MENUITEM "E&xit", ID_FILE_EXIT
END
POPUP "&Edit"
BEGIN
// Insert menu here :p
END
POPUP "&Links"
BEGIN
MENUITEM "&Visit Lukem_95's Website", ID_LINK_WEBSITE
MENUITEM "G&oogle.com", ID_LINK_GOOGLE
END
END
//////////////
相應的 H 檔案
#define IDR_MYMENU 9000 #define ID_FILE_EXIT 9001 #define ID_LINK_WEBSITE 9002 #define ID_LINK_GOOGLE 9003 #define ID_FILE_ABOUT 9004
由於 Win32 API 是基於 C 的,並且也是一個移動目標,並且由於每個 OS 版本都進行了一些更改,因此建立了一些包裝器,在本節中,您將找到一些在 C++ 設定中使用 API 的可用方法,並提供從底層內容到更高級別的常見所需功能實現的抽象,處理 GUI、複雜控制元件甚至通訊和資料庫訪問。
- Microsoft Foundation Classes(MFC);
- 一個用於開發 Windows 應用程式和 UI 元件的 C++ 庫。由 Microsoft 為 C++ Windows 程式設計師建立,作為 Win32 API 的抽象層,在 MFC 上很少使用新的 STL 支援的功能。它也與 Windows CE(OS 的口袋電腦版本)相容。MFC 被設計為使用文件-檢視模式,這是一種模型檢視控制器 (MVC) 模式的變體。
有關 MFC 的更多資訊,請參閱 Windows 程式設計 Wikibook。
- Windows Template Library(WTL);
- 一個用於開發 Windows 應用程式和 UI 元件的 C++ 庫。它擴充套件了 ATL(Active Template Library),並提供了一組用於控制元件、對話方塊、框架視窗、GDI 物件等的類。此庫不受 Microsoft 服務支援(但它在 MS 內部使用,並且可以在 MSDN 上下載)。
- Win32 Foundation Classes(WFC);
- (http://www.samblackburn.com/wfc/) 一個 C++ 類庫,擴充套件了 Microsoft Foundation Classes (MFC) 以執行特定於 NT 的操作。
- Borland Visual Component Library(VCL);
- 一個 Delphi/C++ 庫,用於開發 Windows 應用程式、UI 元件和各種服務應用程式。由 Borland 建立,作為 Win32 API 的抽象層,但也實現了許多非視覺化和非 Windows 特定的物件,例如 AnsiString 類。
通用 GUI/API 包裝器是程式設計庫,它們為作業系統提供統一的平臺中立介面 (API),而與底層平臺無關。此類庫極大地簡化了跨平臺軟體的開發。
使用包裝器作為可移植性層將為應用程式提供一些或所有以下好處
- 獨立於硬體。
- 獨立於作業系統。
- 獨立於對特定版本的更改。
- 獨立於 API 樣式和錯誤程式碼。
跨平臺程式設計不僅僅是 GUI 程式設計。跨平臺程式設計處理 C++ 標準語言未指定的程式碼部分的最低要求,因此程式可以在不同的硬體平臺上編譯和執行。
以下是一些跨平臺 GUI 工具包
- Gtkmm - C GUI 庫 GTK+ 的介面。它在設計上不是跨平臺的,而是多平臺的,即它可以在許多平臺上使用。
- Qt (http://qt-project.org) - 一個跨平臺的(Qt 是 Linux KDE 桌面環境的基礎,支援 X Window System (Unix/X11)、Apple Mac OS X、Microsoft Windows NT/9x/2000/XP/Vista/7 和 Symbian OS),它是一個面向物件的 應用程式開發 框架,廣泛用於開發 GUI 程式(在這種情況下,它被稱為小部件工具包),以及開發非 GUI 程式,如控制檯工具和伺服器。用於許多商業應用程式,例如 Google Earth、Skype for Linux 和 Adobe Photoshop Elements。在 LGPL 或商業許可下發布。
- wxWidgets (http://wxwidgets.org/) - 一個用於建立跨平臺應用程式圖形使用者介面 (GUI) 的小部件工具包,適用於 Win32、Mac OS X、GTK+、X11、Motif、WinCE 等平臺,使用一個程式碼庫。 它可以從 C++、Python、Perl 和 C#/.NET 等語言使用。 與其他跨平臺工具包不同,wxWidgets 應用程式的外觀和感覺是原生的。 這是因為 wxWidgets 使用平臺本身的原生控制元件,而不是模擬它們。 它也具有廣泛性、免費、開源和成熟的特點。 wxWidgets 不僅僅是一個 GUI 開發工具包,它還提供了用於檔案和流、應用程式設定、多執行緒、程序間通訊、資料庫訪問等方面的類。
- FLTK “快速輕量級工具包”
多工處理
[edit | edit source]多工處理 是一個過程,多個任務(也稱為 程序)共享共同的處理資源,例如 CPU。
只有一顆 CPU 的計算機一次只能執行一個程序。 透過“執行”,是指在特定時間點,CPU 正在為該程序積極執行指令。 對於單 CPU 系統,使用 排程 的系統可以實現多工處理,透過該排程,處理器的執行時間被多個程序共享,從而允許每個程序推進它們的計算,看似並行進行。 一個程序執行一段時間,然後另一個等待的程序獲得執行機會。
將 CPU 從一項任務重新分配到另一項任務的行為稱為 上下文切換。 當上下文切換頻繁發生時,就會產生 並行 的假象。
即使在擁有多個 CPU 的計算機上,多處理器 機器上,多工處理也允許執行的任務數超過 CPU 的數量。
作業系統可能採用多種不同的 排程策略,這些策略通常歸類於以下類別
- 在 多道程式設計 系統中,正在執行的任務會一直執行,直到它執行需要等待外部事件的操作(例如從磁帶讀取)或直到計算機的排程程式強制將正在執行的任務從 CPU 中換出。 多道程式設計系統旨在最大限度地提高 CPU 利用率。
- 在 分時 系統中,正在執行的任務需要釋放 CPU,無論是主動釋放還是由外部事件(例如 硬體中斷)導致。 分時系統旨在允許多個程式看似同時執行。 用於定義這種行為的術語“分時”不再使用,已被術語“多工處理”所取代。
- 在 即時 系統中,一些等待的任務被保證在外部事件發生時獲得 CPU。 即時系統旨在控制機械裝置,例如工業機器人,這些機器人需要及時處理。
多工處理已經成功地整合到當前的作業系統中。 今天大多數計算機都支援一次執行多個程序。 對於使用 對稱多處理器 (SMP) 進行 分散式計算 和使用 多核或晶片多處理器 (CMP) 進行計算的系統來說,這是必需的,這些系統中的處理器已經從雙核發展到四核,並且核心數量還會繼續增加。 每種技術都有其特定的侷限性和適用性,但所有這些技術都具有共同的目標,即執行併發處理。
程序
[edit | edit source]程序 是獨立的執行單元,它們包含自己的狀態資訊,使用自己的地址空間,並且只通過 程序間通訊 (IPC) 機制相互互動。 可以說一個程序至少包含一個執行執行緒(不要與完整的執行緒構造混淆)。 程序由託管作業系統在程序資料結構中管理。 能夠同時執行的程序的最大數量取決於作業系統和該系統的可用資源。
子程序
[edit | edit source]子程序(也稱為 生成程序),是由另一個程序(父程序)建立的程序,繼承了父程序的大多數屬性,例如開啟的檔案。 每個程序可以建立多個子程序,但最多隻有一個父程序; 如果一個程序沒有父程序,這通常表示它是直接由 核心 建立的。
在 UNIX 中,子程序實際上是作為父程序的副本建立的(使用 fork)。 然後,子程序可以使用 覆蓋 自己,替換為不同的程式(使用exec),根據需要。 最初的程序稱為 init,由核心在啟動時啟動,並且永遠不會終止; 其他無父程序的程序可能會被啟動以執行 守護程序 任務 使用者空間 中。 程序最終沒有父程序的另一種方式是,如果父程序死亡,留下 孤兒程序; 但在這種情況下,它很快就會被 init 採用。
程序間通訊 (IPC)
[edit | edit source]IPC 通常由作業系統管理。
共享記憶體
[edit | edit source]大多數較新的作業系統都提供某種記憶體保護。 在 Unix 系統中,每個程序都獲得自己的虛擬地址空間,系統反過來保證任何程序都無法訪問另一個程序的記憶體區域。 如果程序發生錯誤,只有該程序記憶體的內容會被破壞。
使用共享記憶體,解決了在不同程序之間啟用對共享資料的隨機訪問的需求。 但是,將給定的記憶體區域宣告為多個程序可以同時訪問,就會引發控制和同步的需求,因為多個程序可能嘗試同時更改此記憶體區域。
多執行緒
[edit | edit source]直到最近,C++ 標準還沒有包含任何關於多執行緒的規範或內建支援。 因此,執行緒 必須使用特殊的執行緒庫來實現,這些庫通常是平臺相關的,作為 C++ 標準的擴充套件。
一些流行的 C++ 執行緒庫包括
(此列表不打算完整。)
- Boost - 此軟體包包含多個庫,其中之一是執行緒(併發程式設計)。 boost 執行緒庫功能不很全,但功能完整、可移植、健壯且符合 C++ 標準的風格。 使用與 BSD 許可證相似的 boost 許可證。
- 英特爾® 執行緒構建塊 (TBB) 提供了一種在 C++ 程式中表達並行性的豐富方法。 該庫可幫助您利用多核處理器的效能,而無需成為執行緒專家。 執行緒構建塊不僅僅是一個執行緒替換庫。 它代表了一種更高層次的任務型並行,它抽象了平臺細節和執行緒機制,以實現效能和可擴充套件性。 它是一個在 GNU 通用公共許可證版本二 (GPLv2) 下發布的開源專案,包含執行時例外。
- 英特爾® Cilk™ Plus (英特爾® Cilk™ Plus) 為 C 和 C++ 語言添加了簡單的語言擴充套件,以表達任務和資料並行。 這些語言擴充套件功能強大,但易於應用並可用於各種應用程式中。
- 自適應通訊環境 (通常稱為 ACE) - 另一個工具包,包含可移植的執行緒抽象以及許多其他設施,所有這些都包含在一個庫中。 開源,在非標準但非限制性的許可下發布。
- ZThreads - 一個可移植的執行緒抽象庫。這個庫功能豐富,只處理併發,並且在 MIT 許可證下開源。
當然,您可以從 C++ 訪問完整的 POSIX 和 C 語言執行緒介面,以及 Windows 上的 API。那麼為什麼還要費心使用一個庫呢?
原因是像鎖這樣的東西是分配的資源,而 C++ 提供了抽象來使管理這些東西更容易。例如,boost::scoped_lock<> 使用物件構造/析構來確保在離開物件的詞法作用域時解鎖互斥鎖。像這樣的類在防止死鎖、競爭條件和其他執行緒程式特有的問題方面非常有用。此外,這些庫使您能夠編寫跨平臺的多執行緒程式碼,而使用平臺特定的函式則無法做到。
在任何使用執行緒方法的情況下,都需要識別熱點,即執行時間最長的程式碼段。為了確定獲得最大效能的最佳機會,可以從自下而上和自上而下兩種方法來確定哪些程式碼段可以並行執行。
在自下而上的方法中,人們只關注程式碼中的熱點。這需要對應用程式的呼叫堆疊進行深入分析,以確定哪些程式碼段可以並行執行並減少熱點。在採用併發的熱點部分,仍然需要將該併發移動到呼叫堆疊中更高的地方,以提高每個執行緒執行的粒度。
使用自上而下的方法,重點關注應用程式的所有部分,以確定哪些計算可以以更高的抽象級別編碼為並行執行。降低抽象級別,直到整體效能提升足以達到必要的目標,這樣做的優點是實現速度快且程式碼可重用性高。這也是為所有計算實現最佳粒度級別的最佳方法。
- 執行緒與程序
執行緒和程序都是並行化應用程式的方法,它們的實現方式可能因不同的作業系統而異。一個程序始終只有一個執行執行緒,也稱為主執行緒。通常,一個執行緒包含在一個程序內(在程序的地址空間內),同一個程序的不同執行緒共享一些資源,而不同的程序則不共享。
原子性是指原子操作,這些操作是不可分割的和/或不可中斷的。即使在單個核心上,也不能假設一個操作是原子的。在這方面,只有在使用匯編器時才能保證操作的原子性。因此,C++ 標準提供了一些保證,作業系統和外部庫也是如此。
原子操作也可以被看作是任何給定的一組操作,這些操作可以組合在一起,使得它們對系統中的其他部分來說就像一個只有一個結果的單個操作:成功或失敗。這完全取決於抽象級別和底層保證。
所有現代處理器都提供基本的原子原語,這些原語然後被用來構建更復雜的原子物件。除了原子讀寫操作外,大多數平臺還提供原子讀寫更新操作,例如test-and-set或compare-and-swap,或者是一對操作,例如load-link/store-conditional,只有在原子地發生時(即沒有中間的衝突更新)才會產生效果。這些可以用來實現鎖,這是多執行緒程式設計中至關重要的機制,它允許跨多個操作組強制執行不變性和原子性。
許多處理器,尤其是具有64 位浮點支援的32 位處理器,提供了一些非原子讀寫操作:一個執行緒讀取一個 64 位暫存器,而另一個執行緒正在寫入它,可能會看到“之前”和“之後”值的組合,這種組合可能從未真正寫入暫存器。此外,只有單個操作保證是原子的;任意執行多個讀寫操作的執行緒也會觀察到“之前”和“之後”值的混合。顯然,當這種效果可能發生時,不能依賴於不變性。
如果沒有處理已知的原子操作,應該依靠編碼的抽象級別的同步原語。
- 示例 - 一個程序
例如,假設一個程序在一個給定的記憶體位置上執行,並遞增該位置中的值。
- 程序讀取記憶體位置中的值;
- 程序將值加 1;
- 程序將新值寫回記憶體位置。
- 示例 - 兩個程序
現在,假設兩個程序正在執行,它們遞增一個共享的記憶體位置。
- 第一個程序讀取記憶體位置中的值;
- 第一個程序將值加 1;
但它還沒有將新值寫回記憶體位置,就被掛起了,第二個程序被允許執行。
- 第二個程序讀取記憶體位置中的值,該值與第一個程序讀取的值相同;
- 第二個程序將值加 1;
- 第二個程序將新值寫入記憶體位置。
第二個程序被掛起,第一個程序被允許再次執行。
- 第一個程序將一個現在錯誤的值寫入記憶體位置,它不知道另一個程序已經更新了記憶體位置中的值。
這是一個簡單的例子。在實際系統中,操作可能更加複雜,引入的錯誤也極其細微。例如,從記憶體中讀取一個 64 位值實際上可能是兩個順序讀取兩個 32 位記憶體位置。如果一個程序只讀取了前 32 位,並且在它讀取後 32 位之前記憶體中的值發生了改變,它將既沒有原始值,也沒有新值,而是一個混合的垃圾值。
此外,程序執行的具體順序可能會改變結果,這使得這種錯誤難以檢測和除錯。
- 作業系統和可移植性
不僅需要考慮底層硬體,還需要考慮不同的作業系統 API。在跨不同作業系統移植程式碼時,應該考慮哪些保證是提供的。在處理外部庫時,也需要類似的考慮。
競爭條件(資料競爭,或者簡稱競爭)發生在從多個執行路徑併發訪問資料時。例如,當多個執行緒對同一個資源(如檔案或記憶體塊)具有共享訪問許可權時,並且至少一個訪問是寫入操作,就會發生這種情況。這會導致它們之間相互干擾。
執行緒程式設計是圍繞謂詞和共享資料構建的。有必要識別所有可能的執行路徑,並識別真正獨立的計算。為了避免問題,最好在儘可能高的級別上實現併發。
大多數競爭條件都是由於對執行緒執行順序的錯誤假設而造成的。在處理共享變數時,永遠不要假設執行緒寫入操作將線上程讀取操作之前執行。如果您需要保證,您應該檢視是否有同步原語可用,如果沒有,您應該自己實現。
鎖定暫時阻止不可共享的資源被同時使用。鎖定可以透過使用同步物件來實現。
執行緒最大的問題之一是鎖定需要分析和理解資料和程式碼之間的關係。這會使軟體開發變得更加複雜,尤其是在針對多個作業系統時。這使得多執行緒程式設計更像是一門藝術,而不是科學。
鎖的數量(取決於同步物件)可能受到作業系統的限制。如果總是以相同的臨界區域訪問資源,則一個鎖可以設定為保護多個資源。
臨界區是指程式碼執行並行化的關鍵區域。該術語用於定義需要與程式中其他程式碼隔離執行的程式碼部分。
這是一個常見的基本概念。這些程式碼部分需要透過同步技術來保護,因為它們會導致競爭條件。
當任何鎖操作導致併發執行緒之間無休止的等待迴圈時,就會發生死鎖。
除了用於保證平行計算的正確執行外,同步是一種開銷。嘗試透過利用執行緒的本地儲存或使用獨佔記憶體位置將其降至最低。
計算粒度被寬鬆地定義為在需要任何同步之前執行的計算量。同步之間的時間越長,計算的粒度越小。在處理並行性的要求時,這意味著更容易擴充套件到更多執行緒,並具有更低的開銷成本。高粒度可能會導致由於同步和一般執行緒開銷的要求而失去使用執行緒的任何優勢。
互斥鎖是互斥的縮寫。它依賴於作業系統(而不是 CPU)提供的同步設施。由於此係統物件在任何給定時間只能由單個執行緒擁有,因此互斥鎖物件有助於防止資料競爭,並允許線上程之間進行執行緒安全的同步資料。透過呼叫其中一個鎖定函式,執行緒獲取互斥鎖物件的擁有權,然後透過呼叫相應的解鎖函式釋放擁有權。互斥鎖可以是遞迴的或非遞迴的,並且可以授予一個或多個執行緒同時擁有權。
訊號量是一個讓步同步物件,可用於同步多個執行緒。這是最常用的同步方法。
自旋鎖是忙等待同步物件,用作互斥鎖的替代品。它們是使用機器相關彙編指令(例如測試並設定)實現的執行緒間鎖定,其中執行緒只需在迴圈中等待(自旋),該迴圈反覆檢查鎖是否可用(忙等待)。這就是為什麼自旋鎖在鎖定時間較短時效能更好的原因。[1] 它們永遠不會在單 CPU 機器上使用。
執行緒本身就是一種程式碼結構,是程式的一部分,使它能夠分支(或拆分)成兩個或多個同時(或偽同時)執行的任務。執行緒使用搶佔式多工處理。
執行緒是作業系統可以為其分配不同的處理器時間(排程)以執行的基本單元(程式碼的最小部分)。這意味著,執行緒實際上並非在任何單核系統上併發執行,而是按順序執行。執行緒通常依賴於作業系統執行緒排程程式來搶佔繁忙執行緒並恢復另一個執行緒。
如今,執行緒不僅是大多數(如果不是全部)現代計算機、程式語言和作業系統支援的關鍵併發模型,而且本身也是硬體進化的核心,例如對稱多處理器,瞭解執行緒現在對所有程式設計師來說都是必需的。
執行緒的執行順序由作業系統的程序排程程式控制;它是非確定性的。程式設計師可用的唯一控制是將優先順序分配給執行緒,但永遠不要假設特定的執行順序。
這種區別是保留用來表示特定執行緒實現訊息對映以響應使用者與應用程式互動時生成的事件和訊息。這在使用 Windows 平臺(Win32 API)時尤其常見,因為它是實現訊息泵的方式。
這種區別旨在指定不直接依賴或不是應用程式圖形使用者介面的一部分的執行緒,並且與主執行執行緒同時執行。
執行緒區域性變數的駐留地,是全域性記憶體的專用部分。每個執行緒(或纖程)都將收到自己的堆疊空間,駐留在不同的記憶體位置。這將包含保留的記憶體和最初提交的記憶體。線上程退出時,這將被釋放,但如果執行緒以其他方式終止,則不會被釋放。
由於程序中的所有執行緒共享相同的地址空間,因此靜態或全域性變數中的資料通常位於相同的記憶體位置,當被來自相同程序的執行緒引用時。軟體必須考慮硬體快取一致性。例如,在多處理器環境中,每個處理器都有一個本地快取。如果不同處理器的執行緒修改駐留在相同快取行的變數,這將使該快取行失效,從而強制執行快取更新,從而影響效能。這被稱為錯誤共享。
這種型別的儲存適用於儲存臨時結果甚至部分結果的變數,因為儘可能地減少部分結果所需的同步次數和頻率將有助於減少同步開銷。
同步可以定義在幾個步驟中,第一步是程序鎖定,其中程序由於發現受保護的資源被鎖定而被暫停執行,鎖定存在成本,尤其是如果鎖定持續時間過長的話。
顯然,如果任何同步機制被過度使用,都會影響效能。由於它們是昂貴的操作,在某些情況下,增加 TLS 的使用而不是僅依賴共享資料結構將減少對同步的需求。
- 臨界區
- 掛起和恢復
- 在物件上同步
- 協作式執行緒與搶佔式執行緒
- 執行緒池

一個 纖程 是一個特別輕量級的 執行執行緒。與執行緒類似,纖程共享 地址空間。然而,纖程使用 協作式多工,纖程在執行時會讓出自己以執行另一個纖程。
- 作業系統支援
纖程需要的 作業系統 支援比執行緒少。它們可以在現代 Unix 系統中使用庫函式 getcontext、setcontext 和 swapcontext(在 ucontext.h 中),例如在 GNU 可移植執行緒 中。
在 Microsoft Windows 上,纖程是使用 ConvertThreadToFiber 和 CreateFiber 呼叫建立的;當前掛起的纖程可以在任何執行緒中恢復。纖程區域性儲存(類似於 執行緒區域性儲存)可用於建立變數的唯一副本。
Symbian OS 在其活動排程器中使用了類似於纖程的概念。一個 活動物件 (Symbian OS) 包含一個纖程,當多個未完成的非同步呼叫完成時,該纖程將由活動排程器執行。多個活動物件可以等待執行(基於優先順序),並且每個活動物件必須限制自己的執行時間。
大多數並行體系結構研究是在 1960 年代和 1970 年代進行的,為今天才開始引起普遍關注的問題提供瞭解決方案。隨著併發程式設計需求的不斷增長,主要得益於當今硬體的演進,我們作為程式設計師被迫實現程式設計模型,以簡化處理舊執行緒模型的複雜過程,並透過抽象問題來節省開發時間。

國際化和本地化 指的是如何將計算機軟體適應其他地區、國家或文化。具體來說,是指那些對程式設計師或主要使用者群不熟悉的地區、國家或文化。
具體來說,國際化處理的是以一種可以配置或適應各種語言和地區的方式設計軟體應用程式的過程,而無需對程式碼庫進行重大更改。另一方面,本地化處理的是透過新增特定於地區的元件和文字翻譯,使軟體能夠配置或自動適應特定地區、時區或語言的過程。
軟體開發人員必須瞭解國際化的複雜性,因為他們編寫了實際的底層程式碼。他們如何使用既定的服務來實現任務目標,決定了專案的整體成功。從根本上說,程式碼和功能設計會影響產品的翻譯和定製方式。因此,軟體開發人員需要了解關鍵的本地化概念。
文字,尤其是用於生成可讀文字的字元,依賴於字元編碼方案,該方案將給定字元集(有時稱為內碼表)中的字元序列與其他事物(例如自然數序列、位元組或電脈衝序列)配對,以便於其數字表示的使用。
一個易於理解的例子是莫爾斯電碼,它將拉丁字母的字母編碼為一系列長短的電報鍵按下;這類似於 ASCII 如何將字母、數字和其他符號編碼為整數。
位元組最常見的用途可能是儲存字元程式碼。在鍵盤上鍵入、在螢幕上顯示和在印表機上列印的字元都具有數值。為了使其能夠與世界其他地區進行通訊,IBM PC 使用了 ASCII 字元集的變體。在 ASCII 字元集 中定義了 128 個程式碼。IBM 使用剩餘的 128 個可能值用於擴充套件字元程式碼,包括歐洲字元、圖形符號、希臘字母和數學符號。
在計算的早期,編碼字元集(如 ASCII (1963) 和 EBCDIC (1964))的引入開始了標準化過程。這些字元集的侷限性很快變得明顯,並且開發了許多臨時方法來擴充套件它們。支援多種書寫系統(語言),包括東亞 CJK 字族,需要支援更多字元,並要求採用系統的方法來進行字元編碼,而不是之前採用的臨時方法。
Unicode 是一種行業標準,其目標是提供一種方法,使所有形式和語言的文字都可以被計算機編碼以供使用。Unicode 6.1 於 2012 年 1 月釋出,是當前版本。它目前包含來自 93 種文字系統的 109,000 多個字元。由於 Unicode 只是一個將數字分配給字元的標準,因此還需要方法將這些數字編碼為位元組。三種最常見的字元編碼是 UTF-8、UTF-16 和 UTF-32,其中 UTF-8 是迄今為止最常用的編碼。
在 Unicode 標準中,平面 是指向特定字元的數值(程式碼點)組。Unicode 程式碼點在邏輯上被劃分為 17 個平面,每個平面有 65,536 (= 216) 個程式碼點。平面由數字 0 到 16decimal 標識,對應於六位格式 (hhhhhh) 中前兩位的可能值 00-10hexadecimal。截至 6.1 版,其中六個平面已分配程式碼點(字元),並已命名。
平面 0 - 基本多語言平面 (BMP)
平面 1 - 補充多語言平面 (SMP)
平面 2 - 補充表意文字平面 (SIP)
平面 3–13 - 未分配
平面 14 - 補充專用平面 (SSP)
平面 15–16 - 補充私有使用區 (S PUA A/B)
| BMP | SMP | ||
|---|---|---|---|
| 0000–0FFF | 8000–8FFF | 10000–10FFF | 18000-18FFF |
| 1000–1FFF | 9000–9FFF | 11000–11FFF | 19000-19FFF |
| 2000–2FFF | A000–AFFF | 12000–12FFF | 1A000-1AFFF |
| 3000–3FFF | B000–BFFF | 13000–13FFF | 1B000-1BFFF |
| 4000–4FFF | C000–CFFF | 14000-14FFF | 1C000-1CFFF |
| 5000–5FFF | D000–DFFF | 15000-15FFF | 1D000–1DFFF |
| 6000–6FFF | E000–EFFF | 16000–16FFF | 1E000–1EFFF |
| 7000–7FFF | F000–FFFF | 17000-17FFF | 1F000–1FFFF |
| SIP | SSP | |
|---|---|---|
| 20000–20FFF | 28000–28FFF | E0000–E0FFF |
| 21000–21FFF | 29000–29FFF | |
| 22000–22FFF | 2A000–2AFFF | |
| 23000–23FFF | 2B000–2BFFF | |
| 24000–24FFF | ||
| 25000–25FFF | ||
| 26000–26FFF | ||
| 27000–27FFF | 2F000–2FFFF | |
目前,大約 10% 的潛在空間已被使用。此外,Unicode 聯盟已經為其能夠識別的所有當前和古代書寫系統(指令碼)預留了字元範圍。雖然 Unicode 最終可能需要使用另外一個備用平面來容納象形文字,但其他平面仍然可以使用。即使發現之前未知的具有數萬個字元的指令碼,1,114,112 個程式碼點的限制在不久的將來也不太可能達到。Unicode 聯盟已經宣告該限制永遠不會改變。
這種奇怪的限制(它不是 2 的冪)不是由於 UTF-8,UTF-8 的設計限制為 231 個程式碼點(32768 個平面),即使限制為 4 位元組也可以編碼 221 個程式碼點(32 個平面),而是由於 UTF-16 的設計。在 UTF-16 中,使用一對包含兩個 16 位 字 的“代理對”來編碼 220 個程式碼點(1 到 16),此外還使用單個字來編碼平面 0。
UTF-8 是 Unicode 的可變長度編碼,每個字元使用 1 到 4 個位元組。它設計為與 ASCII 相容,因此單位元組值在 UTF-8 中代表與 ASCII 中相同的字元。由於 UTF-8 流不包含 '\0',因此您可以直接在現有的 C++ 程式碼中使用它,無需任何移植(除了在計算其“實際”字元數量時)。
UTF-16 也是可變長度的,但它以 16 位為單位而不是 8 位,因此每個字元由 2 或 4 個位元組表示。這意味著它與 ASCII 不相容。
與前兩種編碼不同,UTF-32 不是 可變長度的:每個字元都由正好 32 位表示。這使得編碼和解碼更加容易,因為 4 位元組值直接對映到 Unicode 程式碼空間。缺點是空間效率低下,因為每個字元都佔用 4 個位元組,無論它是什麼。
最佳化可以被認為是提高某事物效能的有針對性的努力,這是工程學中一個重要的概念,特別是我們正在討論的軟體工程。我們將處理特定的計算任務和最佳實踐,以減少資源利用,不僅是系統資源,還包括程式設計師和使用者資源,所有這些都基於從假設的經驗驗證和邏輯步驟中演變而來的最佳解決方案。
所有采取的最佳化步驟都應以減少需求和促程序序目標為目標。任何主張只有透過對給定問題和所應用解決方案的 效能分析 來證實。沒有效能分析,任何最佳化都是毫無意義的。
最佳化通常是程式設計師之間討論的話題,並非所有結論都能達成共識,因為它們與目標、程式設計師經驗和特定設定密切相關。最佳化的程度主要取決於程式設計師做出的行動和決策。這些可以是簡單的事情,從基本的編碼實踐到選擇用來建立程式的工具。即使選擇正確的編譯器也會產生影響。一個好的最佳化編譯器允許程式設計師定義他對最佳化結果的期望;編譯器在最佳化方面的能力取決於程式設計師從生成的編譯中獲得的滿意程度。
最安全的最佳化方法之一是降低複雜性,簡化組織和結構,同時避免程式碼膨脹。這需要規劃的能力,同時又不失去對未來需求的跟蹤,實際上,這是程式設計師在眾多因素之間做出的妥協。
程式碼最佳化技術分為以下幾類
- 高階最佳化
- 演算法最佳化(數學分析)
- 簡化
- 低階最佳化
- 迴圈展開
- 強度折減
- Duff's Device
- 乾淨迴圈
"保持簡單,笨蛋" (KISS) 原則,要求在開發中優先考慮簡單性。它與阿爾伯特·愛因斯坦的一句格言非常相似,格言是,“一切都要儘可能簡單,但不能再簡單了”,對許多采用者來說,困難在於確定應保持何種程度的簡單性。在任何情況下,分析基本和簡單的系統總是更容易的,消除複雜性也將為程式碼重用和更通用的任務和問題處理方法開啟大門。
程式碼清理的大部分益處對於經驗豐富的程式設計師來說應該是顯而易見的,由於採用了良好的程式設計風格指南,它們已經成為第二天性。但就像任何人類活動一樣,會發生錯誤,也會做出例外,因此,在本節中,我們將嘗試記住那些對程式碼最佳化有影響的細微變化。
- 使用虛成員函式
記住虛成員函式對效能的影響(在介紹 virtual 關鍵字 時已經介紹)。當最佳化成為問題時,大多數與最佳化相關的專案設計變更將無法進行,但仍然需要清理遺留工件。確保在類/結構繼承樹的葉子節點中沒有多餘的 virtual(例如:編譯器 最佳化內聯),將允許進行其他最佳化。
當今系統中最大的瓶頸之一是處理記憶體 快取,無論是 CPU 快取 還是物理記憶體資源,即使 分頁 問題越來越少見。由於程式將在設計級別處理的資料(以及負載級別)是高度可預測的,因此最佳最佳化仍然取決於程式設計師。
應該將適當的資料結構儲存在適當的容器中,優先儲存指向物件的指標而不是物件本身,使用“智慧”指標(參見 Boost 庫),不要嘗試將 auto_ptr<> 儲存在 STL 容器中,這是標準不允許的,但已知某些實現錯誤地允許這樣做。
避免從容器中間刪除和插入元素,在容器末尾執行此操作開銷更小。當物件數量未知時,使用 STL 容器;當物件數量已知時,使用靜態陣列或緩衝區。這需要了解每個容器及其 O(x) 保證。
以 STL 容器為例,在使用 (myContainer.empty()) 與 (myContainer.size() == 0) 方面,重要的是要理解,根據容器型別或其實現,size 成員函式可能需要在與零進行比較之前計算物件數量。這在列表型別容器中很常見。
雖然 STL 試圖為一般情況提供最佳解決方案,但如果效能不符合您的要求,請考慮為您的情況編寫自己的最佳解決方案,也許是一個自定義容器(可能基於 vector),它不會呼叫單個物件的解構函式,並使用避免刪除時間開銷的自定義分配器。
使用記憶體預分配可以提高速度,並且可以簡單地記住在允許的情況下使用 STL vector<T>::reserve()。最佳化系統記憶體和目標硬體的使用。在當今的系統中,虛擬記憶體、執行緒和多核(每個核都有自己的快取)以及主記憶體上的 I/O 操作以及移動記憶體所花費的時間都會降低速度。這可能成為效能瓶頸。相反,選擇基於陣列的資料結構(快取一致資料結構),例如 STL vector,因為資料在記憶體中連續儲存,而不是像連結串列這樣的指標連結資料結構。這將避免“交換死亡”,因為程式需要訪問高度碎片化的資料,甚至會幫助大多數現代處理器今天執行的記憶體預取。
儘可能避免按值返回容器,按引用傳遞容器。
安全性總是要付出代價,即使在程式設計中也是如此。對於任何演算法,新增檢查都會導致完成所需的步驟數量增加。隨著語言變得更加複雜和抽象,瞭解所有更細微的細節(並記住它們)會增加獲得所需經驗所需的時間。可悲的是,C++ 語言的一些實現者採取的大多數步驟都缺乏對程式設計師的可見性,並且由於它們在標準語言之外,因此往往不會被學習。請記住熟悉您正在使用的 C++ 實現的任何擴充套件或特殊性。
作為一種將決策權賦予程式設計師的語言,C++ 提供了多個示例,其中可以透過類似但不同的方式實現類似的結果。理解有時微妙的差異很重要。例如,在決定對 訪問 std::vector 的成員 的需求時,您可以選擇 []、at() 或迭代器。所有這些都具有類似的結果,但具有不同的效能成本和安全注意事項。
最佳化也體現在程式碼的有效性上。如果您能夠使用現有的程式碼庫/框架,這些框架供大量程式設計師使用,那麼您可以預期它會更少出現錯誤,並針對您的特定需求進行最佳化。
其中一些程式碼儲存庫以庫的形式提供給程式設計師。請務必考慮依賴關係並檢查實現方式:如果在沒有考慮的情況下使用,這也會導致程式碼膨脹和記憶體佔用增加,以及降低程式碼的可移植性。我們將在本書的 庫部分 中仔細研究它們。
為了提高程式碼重用,您可能需要將程式碼分成更小的部分、檔案或程式碼,請記住,更多檔案和整體複雜性也會增加編譯時間。
在建立函式或演算法來解決特定問題時,有時我們處理的是數學結構,這些數學結構特別適合透過已建立的數學最小化方法進行最佳化,這屬於 最佳化工程分析 的特定領域。
如前所述,當檢查 inline 關鍵字時,它允許定義一種內聯型別的函式,這種函式的工作原理類似於 迴圈展開,以提高程式碼效能。非行內函數需要一個呼叫指令,多個指令來建立堆疊幀,然後還需要多個指令來銷燬堆疊幀並從函式返回。透過複製函式體而不是進行呼叫,機器程式碼的大小會增加,但執行時間會 _減少_。
除了使用 inline 關鍵字宣告行內函數外,最佳化編譯器還可以決定將其他函式也內聯(請參閱 編譯器最佳化 部分)。
如果可移植性不是問題,並且您精通匯編器,您可以使用它來最佳化計算瓶頸,甚至檢視反彙編器的輸出通常也會有助於尋找改進方法。在您的程式碼中使用 ASM 會帶來一些其他問題(例如可維護性),因此請將其作為最佳化過程的最後手段,如果您使用它,請確保記錄您所做的工作。
x86 反彙編 Wikibook 提供了一些使用 x86 ASM 程式碼的 最佳化示例。
有些專案可能需要很長時間才能編譯。要減少完成編譯所需的時間,第一步是檢查您是否有任何硬體缺陷。您的資源(如記憶體)可能不足,或者 CPU 速度很慢,甚至硬碟碎片程度高也會增加編譯時間。
另一方面,問題可能不是由於硬體限制,而是由於您使用的工具,請檢查您是否正在使用適合當前工作的正確工具,檢視您是否擁有最新版本,或者檢視是否確實如此,檢視是否導致問題,一些不相容性可能是由更新引起的。在編譯器中,更新總比舊好,但您應該首先檢查發生了什麼變化以及它是否適合您的目的。
經驗表明,如果您遇到編譯速度慢的問題,您嘗試編譯的程式可能設計不當,請檢查物件依賴項的結構、包含項,並花一些時間來構建自己的程式碼結構,以最大限度地減少更改後的重新編譯次數,如果編譯時間能夠證明這一點。
使用預編譯標頭檔案和外部標頭檔案保護,這將減少編譯器的工作量。
編譯器最佳化 是調整(主要是自動)編譯器輸出的過程,目的是為了改程序序員請求的操作,從而最大限度地減少或最大限度地提高編譯程式的某些屬性 _同時確保結果相同_。透過細化編譯器最佳化,程式設計師可以編寫更直觀的程式碼,並使其以合理的速度執行,例如跳過使用 預遞增/遞減運算子。
一般來說,最佳化沒有也不可能在 C++ 標準中定義。該標準制定了一些規則和最佳實踐,規定了輸入和輸出的規範化。C++ 標準本身允許編譯器在執行任務時具有一定的自由度,因為某些部分被標記為實現相關的,但通常會建立一個基線,即使如此,一些供應商/實現者也會在一些奇特的特徵上進行一些侵入,顯然是為了安全性和最佳化的目的。
需要牢記的一點是,不存在完美的 C++ 編譯器,但大多數最新的編譯器預設情況下會執行一些簡單的最佳化,這些最佳化試圖抽象並利用現有的更深層的硬體最佳化或目標平臺的特定特徵,大多數這些最佳化幾乎總是受歡迎的,但仍然由程式設計師來決定發生了什麼以及它們是否確實有益。因此,強烈建議您檢查編譯器文件,瞭解它的工作原理以及哪些最佳化在程式設計師的控制之下,僅僅因為編譯器理論上可以進行某種最佳化並不意味著它會進行或它會導致最佳化。
程式設計師可以使用最常見的編譯器最佳化選項,這些選項分為三類
- 速度;提高生成的程式碼的執行時效能。這是最常見的最佳化
- 空間;減小生成的程式碼的大小
- 安全;降低資料結構損壞的可能性(例如,確保不會寫入非法陣列元素)
不幸的是,許多“速度”最佳化會使程式碼更大,而許多“空間”最佳化會使程式碼更慢,這被稱為 時空權衡。
自動內聯類似於隱式內聯。內聯可以是最佳化,也可以是最佳化,具體取決於程式碼和所選擇的最佳化選項。
正如我們之前所見,執行時是程式執行的持續時間,從開始到終止。這是所有執行編譯程式碼所需的資源被分配並希望釋放的地方,這是任何要執行的程式的最終目標,因此它應該是最終最佳化的目標。
在過去,計算機記憶體價格昂貴且技術上尺寸有限,並且是程式設計師的稀缺資源。程式設計師花費大量精力來實現複雜的程式並使用盡可能少的這種資源來處理大量資料。如今,現代系統包含足夠的記憶體來滿足大多數用途,但容量需求和期望也隨之增加;因此,最小化記憶體使用量的技術仍然可能至關重要,事實上,隨著移動計算日益重要,操作效能獲得了新的動力。
衡量程式的記憶體使用情況既困難又費時,程式越複雜,獲得良好的指標就越困難。問題的另一方面是,除了最基本和通用的考慮之外,沒有標準基準(並非所有記憶體使用量都相同)或實踐來解決這個問題。
- 請記住在
std::vector(或deque)上使用swap()。
當嘗試減少(或歸零) vector 或 deque 的大小時,使用 swap() 在這種型別的標準容器上,將保證記憶體被釋放,並且不會使用用於增長的開銷緩衝區。它還將避免使用 erase() 或 reserve() 的謬誤,這不會減少記憶體佔用。
始終需要在系統性能和資源消耗之間保持平衡。延遲例項化是一種記憶體節省機制,它將物件初始化推遲到需要它的時候。
請看以下示例
#include <iostream>
class Wheel {
int speed;
public:
int getSpeed(){
return speed;
}
void setSpeed(int speed){
this->speed = speed;
}
};
class Car{
private:
Wheel wheel;
public:
int getCarSpeed(){
return wheel.getSpeed();
}
char const* getName(){
return "My Car is a Super fast car";
}
};
int main(){
Car myCar;
std::cout << myCar.getName() << std::endl;
}
預設情況下,類 Car 的例項化會例項化類 Wheel。整個類的目的是列印汽車的名稱。由於 Wheel 例項沒有用,初始化它完全是浪費資源。
最好將不需要的類的例項化推遲到需要它的時候。修改上面的 Car 類如下
class Car{
private:
Wheel *wheel;
public:
Car() {
wheel=NULL; // a better place would be in the class constructor initialization list
}
~Car() {
delete wheel;
}
int getCarSpeed(){
if (wheel == NULL) {
wheel = new Wheel();
}
return wheel->getSpeed();
}
char const* getName(){
return "My Car is a Super fast car";
}
};
現在,只有在呼叫成員函式 getCarSpeed() 時才會例項化 Wheel。
正如在檢查 執行緒 時所見,它們可以是利用硬體資源並最佳化程式速度效能的一種“簡單”形式。在處理執行緒時,您應該記住它在複雜性、記憶體方面存在成本,如果在需要同步時做錯了,它甚至會降低速度效能,如果設計允許,最好讓執行緒儘可能不受阻礙地執行。

分析是 動態程式分析(與 靜態程式碼分析 相反)的一種形式,它包括使用在程式執行期間收集的資訊來研究程式的行為。它的目的是通常確定要最佳化程式的哪些部分。主要透過確定程式的哪些部分佔用了大部分執行時間,導致訪問資源的瓶頸或訪問這些資源的級別。
在比較應用程式效能時,全域性時鐘執行時間應該是底線。透過檢查執行的漸近階來選擇您的演算法,因為在並行設定中,它們將繼續提供最佳效能。如果您發現一個無法並行化的熱點,即使在檢查呼叫堆疊的更高級別之後也是如此,那麼您應該嘗試找到一個速度較慢但可並行化的演算法。
- 分支預測分析器
- 生成呼叫圖的快取分析器
- 逐行分析
- 堆分析器
- 免費分析工具
- Valgrind(http://valgrind.org/)是一個用於構建動態分析工具的工具框架。包括快取和分支預測分析器、生成呼叫圖的快取分析器和堆分析器。它在以下平臺上執行:X86/Linux、AMD64/Linux、PPC32/Linux、PPC64/Linux 和 X86/Darwin(Mac OS X)。在 GNU 通用公共許可證版本 2 下開源。
- GNU gprof(http://www.gnu.org/software/binutils/)是一個分析工具。該程式首次在 1982 年的 SIGPLAN 編譯器構造研討會上介紹,現在是大多數 UNIX 版本中都可用的 binutils 的一部分。它能夠監控函式(甚至原始碼行)中花費的時間以及對它們的呼叫。在 GNU 通用公共許可證下開源。
- Linux perf(http://perf.wiki.kernel.org/)是一個分析工具,它是 Linux 核心的一部分。它透過取樣操作。
- WonderLeak 是一個高效能 Windows 堆和控制代碼分配分析器,適用於使用 C/C++ API 和 CLI 整合的 x86/x64 本機程式碼開發人員。
- 商業分析工具
- Deleaker(http://deleaker.com/)是一個工具和 Visual Studio 擴充套件,用於查詢記憶體洩漏、控制代碼、GDI 和 USER 物件洩漏。適用於 Windows,支援 x86 / x64。它基於鉤子,不需要程式碼檢測。
過去,所有軟體設計規劃都需要用鉛筆和紙來完成,眾所周知,糟糕的設計會影響產品的質量和可維護性,進而影響上市時間和專案的長期盈利能力。
解決方案似乎是 CASE 和建模工具,它們可以提高設計質量,並幫助輕鬆地實現設計模式,從而幫助提高設計質量、自動文件化並縮短開發週期。
自 80 年代後期和 90 年代初以來,整個軟體工程行業都需要標準化。隨著許多新的競爭軟體設計方法、概念、符號、術語、流程和與之相關的文化的出現和擴散,統一化的需求因大量平行開發而顯而易見。對軟體設計表示的共同基礎的需求迫切需要,為了存檔它,需要對幾何圖形、顏色和描述進行標準化。
UML(統一建模語言)的建立就是為了服務於此目的,它整合了 Booch(Grady Booch 是 UML 的原始開發人員之一,以其在軟體架構、建模和軟體工程流程方面的創新工作而聞名)、OMT、OOSE、Class-Relation 和 OOramand 的概念,將它們融合成一種單一、通用且廣泛適用的建模語言,試圖成為統一的力量,引入了超越程式語言、作業系統、應用領域和程式設計師用來描述和交流所需的底層語義的標準符號。它於 1997 年 11 月被 OMG(物件管理組)採納並得到其支援,已成為行業標準。此後,OMG 呼籲提供有關面向物件方法的資訊,這些資訊可能會建立一種嚴格的軟體建模語言。許多行業領導者真誠地響應了這一呼籲,幫助建立了該標準,UML 的最後一個版本(v2.0)於 2004 年釋出。
UML 仍然被軟體行業和工程界廣泛使用。在後來的日子裡,人們開始意識到(通常被稱為 UML 熱潮)UML 本身存在侷限性,並不是所有工作都適用的工具。需要仔細研究如何以及為什麼使用它,才能使其發揮作用。
- 資源獲取即初始化 (RAII)
- 垃圾回收 (GC)
- 設計模式 - 建立模式、結構模式 和 行為模式。
- 庫 - API 與框架 和 靜態和動態庫。
- Boost 庫
- 最佳化您的程式
- 跨平臺開發
- Win32(又稱 WinAPI) - 包括 Win32 包裝器。
- 跨平臺包裝器
- 多工處理
- 軟體國際化
- 統一建模語言 (UML)
- ↑ Malte Skarupke. "測量互斥鎖、自旋鎖以及 Linux 排程程式到底有多糟糕".




