跳转到内容

C++ 编程 - 第 5 章

来自维基教科书,开放的书本,开放的世界

超越标准

[编辑 | 编辑源代码]

资源获取即初始化 (RAII)

[编辑 | 编辑源代码]

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() 可能会抛出异常,fileexample_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(资源获取即初始化)的强大习惯用法,它可以用来安全地自动管理资源,包括内存。


Clipboard

待办事项
在库中添加一些示例,并扩展此信息。

编程模式

[编辑 | 编辑源代码]

软件设计模式是帮助构建系统设计的抽象。虽然这不是新事物,因为这个概念已经由克里斯托弗·亚历山大在其建筑理论中描述过,但由于设计模式:可复用面向对象软件的要素一书于 1994 年 10 月由Erich GammaRichard HelmRalph JohnsonJohn 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开始,它是一个抽象基类(接口)及其派生类:LaptopDesktop

 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()方法,并由工厂方法返回该对象。

  • 这是原型方法的示例实现。我们这里有所有组件的详细描述。
    • Record类,它是一个纯虚类,它具有一个纯虚方法clone()
    • CarRecordBikeRecordPersonRecordRecord类的具体实现。
    • 一个enum RecordType,它与Record类的每个具体实现一一对应。
    • RecordFactory类具有一个Factory方法CreateRecord(…)。此方法需要一个enum RecordType作为参数,并根据此参数返回Record类的具体实现。
/** 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;
 };

单例的变体


Clipboard

待办事项
讨论迈尔斯单例和其他任何变体。


单例类的应用

单例设计模式的一个常见用途是应用程序配置。配置可能需要全局访问,并且可能需要对应用程序配置进行未来扩展。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;
}

注意
在上面的示例中,第一次调用Singleton::GetInstance将初始化单例实例。此示例仅用于说明目的;对于除简单的示例程序以外的任何内容,此代码都包含错误。

结构型模式

[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 在文献中被描述为表达抽象的强大工具[需要引用]

基于接口的编程 (IBP)

[编辑 | 编辑源代码]

基于接口的编程与模块化编程和面向对象编程密切相关,它将应用程序定义为相互耦合的模块(相互连接并通过接口相互插入)的集合。模块可以被拔掉、替换或升级,而无需损害其他模块的内容。

整个系统的复杂性大大降低。基于接口的编程在模块化编程的基础上更进一步,它坚持要求为这些模块添加接口。因此,整个系统被视为组件以及帮助它们协同工作的接口。

基于接口的编程增加了应用程序的模块化,从而提高了其在后期开发周期中的可维护性,尤其是在每个模块都必须由不同的团队开发的情况下。这是一种众所周知的、已经存在很长时间的方法,它是诸如 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();
}

解释器

[编辑 | 编辑源代码]

给定一种语言,定义其语法的表示以及一个解释器,该解释器使用该表示来解释该语言中的句子。


Clipboard

待办事项
最好让读者参考lexyacc,或者其衍生工具(如 flex 和 bison),以了解解决这些问题的另一种(久经考验?)方法。



#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;


Clipboard

待办事项

  • 讨论 STL 中的迭代器,以及迭代器在算法库中的作用。
  • 迭代器最佳实践
  • 关于创建和使用迭代器的警告
  • 何时使用 operator[] 会更好并简化理解
  • 关于模板对生成代码大小的影响的注意事项(这可能成为一篇不错的学生研究论文)


以下程序给出了使用通用模板的迭代器设计模式的实现

/************************************************************************/
/* 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;
}

模型 - 视图 - 控制器 (MVC)

[编辑 | 编辑源代码]

一种模式,经常被需要维护同一数据的多个视图的应用程序使用。 模型 - 视图 - 控制器模式直到最近[需要引用] 是一种非常常见的模式,尤其是针对图形用户界面编程,它将代码分成三部分。 模型、视图和控制器。

模型是实际的数据表示(例如,数组与链表)或其他表示数据库的对象。 视图是读取模型的接口或胖客户端 GUI。 控制器提供了更改或修改数据的接口,然后选择“下一个最佳视图”(NBV)。

新手可能会将这种“MVC”模型视为浪费,主要是因为您在运行时使用许多额外的对象,而一个大型对象似乎就足够了。 但是 MVC 模式的秘密不在于编写代码,而在于维护它,并允许人们修改代码而不改变其他太多东西。 此外,请记住,不同的开发人员有不同的优势和劣势,因此围绕 MVC 建立团队更容易。 想象一下一个负责精美视图的视图团队,一个了解大量数据的模型团队,以及一个了解应用程序流程全局、处理请求、与模型合作以及为该客户端选择最合适的下一个视图的控制器团队。


Clipboard

待办事项
嗯,有人请想出一个比以下更好的例子……我想不出任何例子

  • 也许是一个银行程序,用于客户访问其账户:传统浏览器上的 Web UI 与移动应用程序? 然后处理支票账户/储蓄账户与贷款与循环信用账户? fwiw


例如:一个简单的中心数据库可以使用一个“模型”进行组织,例如,一个简单的数组。 但是,稍后,使用链表可能更适用。 所有数组访问都必须重新制作为其各自的链表形式(例如,您将 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]
Clipboard

待办事项
在这里定义“静态库”、“动态库”和“静态二进制文件”。


使用静态二进制文件的优点

  • 简化程序分发(文件更少)。
  • 代码简化(不需要动态库中所需的版本检查)。
  • 只编译使用的代码。

使用静态二进制文件的缺点

  • 资源浪费:生成更大的二进制文件,因为库被编译到可执行文件中。浪费内存,因为库不能在进程之间共享(在内存中)(取决于操作系统)。
  • 程序不会从库中的错误修复或扩展中获益,除非重新编译。
库的二进制/源代码兼容性

如果动态链接到该库的早期版本的程序能够继续使用同一库的另一个版本运行,则该库被称为二进制兼容。如果程序需要重新编译才能与每个新版本的库一起运行,则该库被称为源代码兼容

生成二进制兼容库有利于分发,但程序员更难维护。如果库只有源代码兼容,通常最好进行静态链接,因为它不会给最终用户带来问题。

二进制兼容性节省了大量麻烦,并表明该库已达到稳定状态。它使为特定平台分发软件变得更容易。如果不确保版本之间的二进制兼容性,人们将被迫提供静态链接的二进制文件。

仅头文件库

另一个关于库的常见区别是它们是如何分发的(关于结构和使用)。一个仅包含在头文件中的库被称为仅头文件库。这通常意味着它们更简单且易于使用,但是对于复杂的代码来说,这不是理想的解决方案。它不仅会影响可读性,还会导致编译时间更长。此外,由于生成的内联,编译器及其优化能力(或选项)可能会生成更大的二进制文件。这在主要使用模板实现的库中可能并不那么重要。仅头文件库始终包含实现的源代码,商业库很少。

示例:配置 MS Visual C++ 以使用外部库

[edit | edit source]

Boost 库被用作示例库。

注意
Boost.org有一个名为“在 Windows 上入门”的安装指南,该指南指出了由BoostPro Computing提供的自动安装(通常支持先前和较旧的发布版本),还指出,如果使用“源代码和文档”选项取消选择(默认情况下选中),它将不会显示 libs/ 子目录。这将禁用用户重新构建不只是头文件的库部分。这使得按照本节所示的方法自行安装成为最佳选择。

假设您已经解压缩并构建了 Boost 库的二进制部分。以下是要执行的步骤。

包含目录

[edit | edit source]

设置包含目录。它包含头文件(.h/hpp),这些文件描述库接口。

include directories

库目录

[edit | edit source]

设置库目录。它包含预编译的库文件(.lib)。

library directories

库文件

[edit | edit source]

在要使用的库的附加依赖项中输入库文件名。

library filenames (the Boost "REGEXP"-library in this example)



一些库(例如 Boost)使用自动链接来自动选择用于链接的库文件,具体取决于包含的头文件。如果您的编译器支持自动链接,则无需手动选择这些库的文件名。

动态库

[edit | edit source]

对于动态加载(.dll)库,还必须将 DLL 文件放在与可执行文件相同的文件夹中,或者放在系统路径中。

运行时库

[edit | edit source]

库还必须使用与项目中使用的相同的运行时库进行编译。因此,许多库都有不同的版本,具体取决于它们是为单线程多线程运行时以及调试发布运行时编译的,以及它们是否包含调试符号

selection of run-time library

Boost 项目(https://boost.ac.cn/)提供免费的 同行评审开源 ,用于扩展 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++ 中 泛型编程元编程 的广泛工作和研究的来源。

按类别分类的库

并发编程
... TODO
  • 算法
  • 并发编程(线程
  • 容器
    • array - 使用 STL 容器语义管理固定大小的数组
    • Boost 图形库 (BGL) - 泛型图形容器、组件和算法
    • multi-array - 简化 N 维数组的创建
    • 多索引容器 - 带有内置索引的容器,允许不同的排序和访问语义
    • 指针容器 - 模仿大多数标准 STL 容器的容器,允许透明地管理指向值的指针
    • 属性映射 - 以概念形式提供的接口规范和用于将键值映射到对象的通用接口
    • variant - 一种安全的泛型基于堆栈的对象容器,允许高效地存储和访问类型为可从编译时指定的类型集中选择的类型,并且必须在编译时指定。
  • 正确性和 测试
    • 概念检查 - 允许强制执行实际的模板参数要求(概念)
    • 静态断言 - 编译时断言支持
    • Boost 测试库 - 一组匹配的组件,用于编写测试程序,将测试组织成测试用例和测试套件,并控制其运行时执行
  • 数据结构
  • 函数对象和 高阶编程
    • bindmem_fn - 用于函数、函数对象、函数指针和成员函数的通用绑定器
    • function - 用于延迟调用的函数对象包装器。此外,还提供了一种用于回调的通用机制
    • functional - 对 C++ 标准库中指定的函数对象适配器进行了增强,包括
    • hash - C++ 技术报告 1 (TR1) 中指定的哈希函数对象的实现。可以用作无序关联容器的默认哈希函数
    • lambda - 本着 lambda 抽象 的精神,允许在调用站点定义小的匿名函数对象和对这些对象的运算,使用占位符,特别是用于从算法中延迟回调。
    • ref - 提供实用程序类模板,以增强标准 C++ 引用功能,特别是用于泛型函数
    • result_of - 有助于确定调用表达式的类型
    • Signals2 - 管理的信号和槽回调实现
  • 泛型编程
  • 图形
  • 输入/输出
  • 跨语言支持(适用于 Python
  • 迭代器
    • 迭代器
    • operators - 类模板,有助于为用户定义的迭代器和可以参与算术计算的类定义重载运算符。
    • tokenizer - 提供对序列中包含的一组标记的视图,使它们看起来像具有迭代器访问的容器
  • 数学和数值
  • 内存
    • 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 - 有助于确定调用表达式的类型
  • 其他
  • 解析器
  • 预处理器元编程
  • 字符串 和文本处理
    • lexical_cast - 文本之间的类型转换
    • format - 根据格式字符串进行类型安全的参数格式化
    • iostreams - 用于新源/接收器、过滤器框架的 C++ 流和流缓冲区帮助
    • regex - 对正则表达式的支持
    • Spirit - 面向对象的递归下降解析器生成框架
    • 字符串算法 - 一组与字符串相关的各种算法
    • tokenizer - 允许将字符串或其他字符序列划分为 标记
    • wave - 符合标准的强制性 C99 / C++ 预处理器功能的实现,打包在易于使用的接口后面
  • 模板元编程
    • mpl - 一个通用的高级元编程框架,包含编译时算法、序列和元函数
    • 静态断言 - 编译时断言支持
    • 类型特征 - 定义类型基本属性的模板
  • 针对有问题的编译器的变通方法

当前的 Boost 版本包含 87 个独立的库,包括以下三个

不可复制

[编辑 | 编辑源代码]

boost::noncopyable实用程序类 确保类对象永远不会被复制.

class C : boost::noncopyable
{
  ...
};

线性代数 - uBLAS

[编辑 | 编辑源代码]

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.Random

[编辑 | 编辑源代码]

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 随机数库

多线程 - Boost.Thread

[编辑 | 编辑源代码]

演示创建线程的示例代码

#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;
}

跨平台开发

[编辑 | 编辑源代码]

本节旨在向程序员介绍跨多个操作系统环境的可移植性编程。在当今世界,将应用程序限制在单个操作系统或计算机平台似乎不合适,并且越来越需要以跨平台方式进行编程。

Windows 32 API

[编辑 | 编辑源代码]

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

[编辑 | 编辑源代码]

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 库(DLL)

[编辑 | 编辑源代码]

在 Windows 中,库代码以多种形式存在,并且可以通过多种方式访问。

通常,唯一需要的是在源代码中包含相应的头文件,以便向编译器提供信息,并且在链接阶段会链接到 .lib 文件。

此 .lib 文件包含要静态链接到已编译的目标代码的代码,或者包含允许访问系统上动态链接到二进制库 (.DLL) 的代码。

也可以在 C++ 中通过包含适当的信息(如编译和链接时的导入/导出表)来生成二进制库 .DLL。

DLL 代表动态链接库,是某些程序中使用的基本函数文件。许多较新的 C++ IDE(如 Dev-CPP)支持此类库。

Windows 上的常用库包括平台软件开发工具包、Microsoft Foundation Class 和 .Net Framework 程序集的 C++ 接口提供的库。

尽管严格意义上不作为库代码使用,但平台 SDK 和其他库提供了一组标准化的接口,用于访问通过 组件对象模型 实现的 Windows 对象。


Clipboard

待办事项
添加 注册表IO(输入/输出)安全进程和线程


API 约定和 Win32 API 函数(按关注点)

[编辑 | 编辑源代码]

时间测量必须来自 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>) 创建目录,仅用于创建/强制给定目录树或多个目录的存在,或者如果链接已经存在,请注意它是单线程的。


Clipboard

待办事项
添加 构建“窗口”的基础知识窗口事件处理资源


网络应用程序通常在 Windows 上使用 WinSock API 函数使用 C++ 构建。

资源文件可能是 WIN32 API 中最有用元素之一,它们是我们编写菜单、添加图标、背景、音乐以及为我们的程序添加更多美观元素的方式。遗憾的是,在编译中使用资源文件的做法是今天那些使用 MS Visual Studio IDE(资源编辑器、资源结构理解)的人所使用的。

注意
这类似于 QT 库在处理 GUI 元素方面的方法,但在这种情况下,OS 编程 API 直接支持处理资源元素。

资源在 .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 包装器

[编辑 | 编辑源代码]

由于 Win32 API 是基于 C 的,并且也是一个移动目标,并且由于每个 OS 版本都进行了一些更改,因此创建了一些包装器,在本节中,您将找到一些在 C++ 设置中使用 API 的可用方法,并提供从底层内容到更高级别的常见所需功能实现的抽象,处理 GUI、复杂控件甚至通信和数据库访问。

Microsoft Foundation ClassesMFC);
一个用于开发 Windows 应用程序和 UI 组件的 C++ 库。由 Microsoft 为 C++ Windows 程序员创建,作为 Win32 API 的抽象层,在 MFC 上很少使用新的 STL 支持的功能。它也与 Windows CE(OS 的口袋电脑版本)兼容。MFC 被设计为使用文档-视图模式,这是一种模型视图控制器 (MVC) 模式的变体。
有关 MFC 的更多信息,请参阅 Windows 编程 Wikibook。
Windows Template LibraryWTL);
一个用于开发 Windows 应用程序和 UI 组件的 C++ 库。它扩展了 ATL(Active Template Library),并提供了一组用于控件、对话框、框架窗口、GDI 对象等的类。此库不受 Microsoft 服务支持(但它在 MS 内部使用,并且可以在 MSDN 上下载)。
Win32 Foundation ClassesWFC);
(http://www.samblackburn.com/wfc/) 一个 C++ 类库,扩展了 Microsoft Foundation Classes (MFC) 以执行特定于 NT 的操作。
Borland Visual Component LibraryVCL);
一个 Delphi/C++ 库,用于开发 Windows 应用程序、UI 组件和各种服务应用程序。由 Borland 创建,作为 Win32 API 的抽象层,但也实现了许多非可视化和非 Windows 特定的对象,例如 AnsiString 类。

注意
还有更多通用包装器,它们不专注于 Windows API,例如 Qt(框架)WxWidgets,这些在本书的 通用包装器部分 中介绍。

通用包装器

[编辑 | 编辑源代码]

通用 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++0x 标准支持多线程,减少了对了解多种 API 的需求,并提高了代码的可移植性。

一些流行的 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-setcompare-and-swap,或者是一对操作,例如load-link/store-conditional,只有在原子地发生时(即没有中间的冲突更新)才会产生效果。这些可以用来实现,这是多线程编程中至关重要的机制,它允许跨多个操作组强制执行不变性和原子性。

许多处理器,尤其是具有64 位浮点支持的32 位处理器,提供了一些非原子读写操作:一个线程读取一个 64 位寄存器,而另一个线程正在写入它,可能会看到“之前”和“之后”值的组合,这种组合可能从未真正写入寄存器。此外,只有单个操作保证是原子的;任意执行多个读写操作的线程也会观察到“之前”和“之后”值的混合。显然,当这种效果可能发生时,不能依赖于不变性。

如果没有处理已知的原子操作,应该依靠编码的抽象级别的同步原语。

示例 - 一个进程

例如,假设一个进程在一个给定的内存位置上运行,并递增该位置中的值。

  1. 进程读取内存位置中的值;
  2. 进程将值加 1;
  3. 进程将新值写回内存位置。
示例 - 两个进程

现在,假设两个进程正在运行,它们递增一个共享的内存位置。

  1. 第一个进程读取内存位置中的值;
  2. 第一个进程将值加 1;

但它还没有将新值写回内存位置,就被挂起了,第二个进程被允许运行。

  1. 第二个进程读取内存位置中的值,该值与第一个进程读取的值相同
  2. 第二个进程将值加 1;
  3. 第二个进程将新值写入内存位置。

第二个进程被挂起,第一个进程被允许再次运行。

  1. 第一个进程将一个现在错误的值写入内存位置,它不知道另一个进程已经更新了内存位置中的值。

这是一个简单的例子。在实际系统中,操作可能更加复杂,引入的错误也极其细微。例如,从内存中读取一个 64 位值实际上可能是两个顺序读取两个 32 位内存位置。如果一个进程只读取了前 32 位,并且在它读取后 32 位之前内存中的值发生了改变,它将既没有原始值,也没有新值,而是一个混合的垃圾值。

此外,进程运行的具体顺序可能会改变结果,这使得这种错误难以检测和调试。

操作系统和可移植性

不仅需要考虑底层硬件,还需要考虑不同的操作系统 API。在跨不同操作系统移植代码时,应该考虑哪些保证是提供的。在处理外部库时,也需要类似的考虑。

注意
例如,在 Macintosh 上,设置文件位置的调用是原子的,而在 Windows 上,它是一对调用。

竞争条件

[编辑 | 编辑源代码]

竞争条件数据竞争,或者简称竞争)发生在从多个执行路径并发访问数据时。例如,当多个线程对同一个资源(如文件或内存块)具有共享访问权限时,并且至少一个访问是写入操作,就会发生这种情况。这会导致它们之间相互干扰。

线程编程是围绕谓词和共享数据构建的。有必要识别所有可能的执行路径,并识别真正独立的计算。为了避免问题,最好在尽可能高的级别上实现并发。

大多数竞争条件都是由于对线程运行顺序的错误假设而造成的。在处理共享变量时,永远不要假设线程写入操作将在线程读取操作之前执行。如果您需要保证,您应该查看是否有同步原语可用,如果没有,您应该自己实现。

锁定暂时阻止不可共享的资源被同时使用。锁定可以通过使用同步对象来实现。

线程最大的问题之一是锁定需要分析和理解数据和代码之间的关系。这会使软件开发变得更加复杂,尤其是在针对多个操作系统时。这使得多线程编程更像是一门艺术,而不是科学。

锁的数量(取决于同步对象)可能受到操作系统的限制。如果总是以相同的临界区域访问资源,则一个锁可以设置为保护多个资源。

临界区
[编辑 | 编辑源代码]

临界区是指代码执行并行化的关键区域。该术语用于定义需要与程序中其他代码隔离执行的代码部分。

这是一个常见的基本概念。这些代码部分需要通过同步技术来保护,因为它们会导致竞争条件

当任何锁操作导致并发线程之间无休止的等待循环时,就会发生死锁

除了用于保证并行计算的正确执行外,同步是一种开销。尝试通过利用线程的本地存储或使用独占内存位置将其降至最低。

计算粒度
[编辑 | 编辑源代码]

计算粒度被宽松地定义为在需要任何同步之前执行的计算量。同步之间的时间越长,计算的粒度越小。在处理并行性的要求时,这意味着更容易扩展到更多线程,并具有更低的开销成本。高粒度可能会导致由于同步和一般线程开销的要求而失去使用线程的任何优势。

互斥锁
[编辑 | 编辑源代码]

互斥锁互斥的缩写。它依赖于操作系统(而不是 CPU)提供的同步设施。由于此系统对象在任何给定时间只能由单个线程拥有,因此互斥锁对象有助于防止数据竞争,并允许在线程之间进行线程安全的同步数据。通过调用其中一个锁定函数,线程获取互斥锁对象的拥有权,然后通过调用相应的解锁函数释放拥有权。互斥锁可以是递归的或非递归的,并且可以授予一个或多个线程同时拥有权。

信号量
[编辑 | 编辑源代码]

信号量是一个让步同步对象,可用于同步多个线程。这是最常用的同步方法。

自旋锁
[编辑 | 编辑源代码]

自旋锁忙等待同步对象,用作互斥锁的替代品。它们是使用机器相关汇编指令(例如测试并设置)实现的线程间锁定,其中线程只需在循环中等待(自旋),该循环反复检查锁是否可用(忙等待)。这就是为什么自旋锁在锁定时间较短时性能更好的原因。[1] 它们永远不会在单 CPU 机器上使用。

线程本身就是一种代码结构,是程序的一部分,使它能够分支(或拆分)成两个或多个同时(或伪同时)运行的任务。线程使用抢占式多任务处理

线程是操作系统可以为其分配不同的处理器时间(调度)以执行的基本单元(代码的最小部分)。这意味着,线程实际上并非在任何单核系统上并发运行,而是按顺序运行。线程通常依赖于操作系统线程调度程序来抢占繁忙线程并恢复另一个线程。

如今,线程不仅是大多数(如果不是全部)现代计算机、编程语言和操作系统支持的关键并发模型,而且本身也是硬件进化的核心,例如对称多处理器,了解线程现在对所有程序员来说都是必需的。

线程的执行顺序由操作系统的进程调度程序控制;它是非确定性的。程序员可用的唯一控制是将优先级分配给线程,但永远不要假设特定的执行顺序。


Clipboard

待办事项
线程量子


用户界面线程
[编辑 | 编辑源代码]

这种区别是保留用来表示特定线程实现消息映射以响应用户与应用程序交互时生成的事件和消息。这在使用 Windows 平台(Win32 API)时尤其常见,因为它是实现消息泵的方式。

工作线程
[编辑 | 编辑源代码]

这种区别旨在指定不直接依赖或不是应用程序图形用户界面的一部分的线程,并且与主执行线程同时运行。

线程本地存储 (TLS)
[编辑 | 编辑源代码]

线程局部变量的驻留地,是全局内存的专用部分。每个线程(或纤程)都将收到自己的堆栈空间,驻留在不同的内存位置。这将包含保留的内存和最初提交的内存。在线程退出时,这将被释放,但如果线程以其他方式终止,则不会被释放。

由于进程中的所有线程共享相同的地址空间,因此静态或全局变量中的数据通常位于相同的内存位置,当被来自相同进程的线程引用时。软件必须考虑硬件缓存一致性。例如,在多处理器环境中,每个处理器都有一个本地缓存。如果不同处理器的线程修改驻留在相同缓存行的变量,这将使该缓存行失效,从而强制执行缓存更新,从而影响性能。这被称为错误共享

这种类型的存储适用于存储临时结果甚至部分结果的变量,因为尽可能地减少部分结果所需的同步次数和频率将有助于减少同步开销。

线程同步
[编辑 | 编辑源代码]

同步可以定义在几个步骤中,第一步是进程锁定,其中进程由于发现受保护的资源被锁定而被暂停执行,锁定存在成本,尤其是如果锁定持续时间过长的话。

显然,如果任何同步机制被过度使用,都会影响性能。由于它们是昂贵的操作,在某些情况下,增加 TLS 的使用而不是仅依赖共享数据结构将减少对同步的需求。

临界区


Clipboard

待办事项
我的 w:临界区,看看关于守卫或监视器部分 ?


挂起和恢复
在对象上同步
协作式线程与抢占式线程
线程池
一个简单的线程池。任务队列中有许多等待的任务(蓝色圆圈)。当队列中出现一个空闲线程(带有虚线圆圈的绿色框)时,一个任务就会从队列中取出,并且空闲线程会执行它(绿色框中的红色圆圈)。完成的任务然后“离开”线程池并加入完成的任务列表(黄色圆圈)。

一个 纤程 是一个特别轻量级的 执行线程。与线程类似,纤程共享 地址空间。然而,纤程使用 协作式多任务,纤程在执行时会让出自己以运行另一个纤程。

操作系统支持

纤程需要的 操作系统 支持比线程少。它们可以在现代 Unix 系统中使用库函数 getcontextsetcontextswapcontext(在 ucontext.h 中),例如在 GNU 可移植线程 中。

Microsoft Windows 上,纤程是使用 ConvertThreadToFiberCreateFiber 调用创建的;当前挂起的纤程可以在任何线程中恢复。纤程局部存储(类似于 线程局部存储)可用于创建变量的唯一副本。

Symbian OS 在其活动调度器中使用了类似于纤程的概念。一个 活动对象 (Symbian OS) 包含一个纤程,当多个未完成的异步调用完成时,该纤程将由活动调度器执行。多个活动对象可以等待执行(基于优先级),并且每个活动对象必须限制自己的执行时间。

利用并行性

[编辑 | 编辑源代码]

大多数并行体系结构研究是在 1960 年代和 1970 年代进行的,为今天才开始引起普遍关注的问题提供了解决方案。随着并发编程需求的不断增长,主要得益于当今硬件的演进,我们作为程序员被迫实现编程模型,以简化处理旧线程模型的复杂过程,并通过抽象问题来节省开发时间。


Clipboard

待办事项
扩展


OpenMP 结构图。


Clipboard

待办事项
扩展

软件国际化

[编辑 | 编辑源代码]

国际化和本地化 指的是如何将计算机软件适应其他地区、国家或文化。具体来说,是指那些对程序员或主要用户群不熟悉的地区、国家或文化。

具体来说,国际化处理的是以一种可以配置或适应各种语言和地区的方式设计软件应用程序的过程,而无需对代码库进行重大更改。另一方面,本地化处理的是通过添加特定于地区的组件和文本翻译,使软件能够配置或自动适应特定地区、时区或语言的过程。

软件开发人员必须了解国际化的复杂性,因为他们编写了实际的底层代码。他们如何使用既定的服务来实现任务目标,决定了项目的整体成功。从根本上说,代码和功能设计会影响产品的翻译和定制方式。因此,软件开发人员需要了解关键的本地化概念。

文本编码

[编辑 | 编辑源代码]

文本,尤其是用于生成可读文本的字符,依赖于字符编码方案,该方案将给定字符集(有时称为代码页)中的字符序列与其他事物(例如自然数序列、字节或电脉冲序列)配对,以便于其数字表示的使用。

一个易于理解的例子是莫尔斯电码,它将拉丁字母的字母编码为一系列长短的电报键按下;这类似于 ASCII 如何将字母、数字和其他符号编码为整数。

文本和数据

[编辑 | 编辑源代码]

字节最常见的用途可能是保存字符代码。在键盘上键入、在屏幕上显示和在打印机上打印的字符都具有数值。为了使其能够与世界其他地区进行通信,IBM PC 使用了 ASCII 字符集的变体。在 ASCII 字符集 中定义了 128 个代码。IBM 使用剩余的 128 个可能值用于扩展字符代码,包括欧洲字符、图形符号、希腊字母和数学符号。

在计算的早期,编码字符集(如 ASCII (1963) 和 EBCDIC (1964))的引入开始了标准化过程。这些字符集的局限性很快变得明显,并且开发了许多临时方法来扩展它们。支持多种书写系统(语言),包括东亚 CJK 字族,需要支持更多字符,并要求采用系统的方法来进行字符编码,而不是之前采用的临时方法。

关于 UNICODE 的内容是什么?

[编辑 | 编辑源代码]

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

[编辑 | 编辑源代码]
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

ISP 和 SSP

[编辑 | 编辑源代码]
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  
PUA
F0000–F0FFF F8000–F8FFF 100000–100FFF 108000–108FFF
F1000–F1FFF F9000–F9FFF 101000–101FFF 109000–109FFF
F2000–F2FFF FA000–FAFFF 102000–102FFF 10A000–10AFFF
F3000–F3FFF FB000–FBFFF 103000–103FFF 10B000–10BFFF
F4000–F4FFF FC000–FCFFF 104000–104FFF 10C000–10CFFF
F5000–F5FFF FD000–FDFFF 105000–105FFF 10D000–10DFFF
F6000–F6FFF FE000–FEFFF 106000–106FFF 10E000–10EFFF
F7000–F7FFF FF000–FFFFF 107000–107FFF 10F000–10FFFF

目前,大约 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() 或迭代器。所有这些都具有类似的结果,但具有不同的性能成本和安全注意事项。

代码重用

[编辑 | 编辑源代码]

优化也体现在代码的有效性上。如果您能够使用现有的代码库/框架,这些框架供大量程序员使用,那么您可以预期它会更少出现错误,并针对您的特定需求进行优化。

其中一些代码存储库以库的形式提供给程序员。请务必考虑依赖关系并检查实现方式:如果在没有考虑的情况下使用,这也会导致代码膨胀和内存占用增加,以及降低代码的可移植性。我们将在本书的 库部分 中仔细研究它们。

为了提高代码重用,您可能需要将代码分成更小的部分、文件或代码,请记住,更多文件和整体复杂性也会增加编译时间。

函数和算法优化

[编辑 | 编辑源代码]

在创建函数或算法来解决特定问题时,有时我们处理的是数学结构,这些数学结构特别适合通过已建立的数学最小化方法进行优化,这属于 优化工程分析 的特定领域。


Clipboard

待办事项
用小例子扩展


如前所述,当检查 inline 关键字时,它允许定义一种内联类型的函数,这种函数的工作原理类似于 循环展开,以提高代码性能。非内联函数需要一个调用指令,多个指令来创建堆栈帧,然后还需要多个指令来销毁堆栈帧并从函数返回。通过复制函数体而不是进行调用,机器代码的大小会增加,但执行时间会 _减少_。

除了使用 inline 关键字声明内联函数外,优化编译器还可以决定将其他函数也内联(请参阅 编译器优化 部分)。

如果可移植性不是问题,并且您精通汇编器,您可以使用它来优化计算瓶颈,甚至查看反汇编器的输出通常也会有助于寻找改进方法。在您的代码中使用 ASM 会带来一些其他问题(例如可维护性),因此请将其作为优化过程的最后手段,如果您使用它,请确保记录您所做的工作。

x86 反汇编 Wikibook 提供了一些使用 x86 ASM 代码的 优化示例

注意
如果使用 gcc 编译器,-S 选项将输出编译生成的汇编代码。

减少编译时间

[编辑 | 编辑源代码]

有些项目可能需要很长时间才能编译。要减少完成编译所需的时间,第一步是检查您是否有任何硬件缺陷。您的资源(如内存)可能不足,或者 CPU 速度很慢,甚至硬盘碎片程度高也会增加编译时间。

另一方面,问题可能不是由于硬件限制,而是由于您使用的工具,请检查您是否正在使用适合当前工作的正确工具,查看您是否拥有最新版本,或者查看是否确实如此,查看是否导致问题,一些不兼容性可能是由更新引起的。在编译器中,更新总比旧好,但您应该首先检查发生了什么变化以及它是否适合您的目的。

经验表明,如果您遇到编译速度慢的问题,您尝试编译的程序可能设计不当,请检查对象依赖项的结构、包含项,并花一些时间来构建自己的代码结构,以最大限度地减少更改后的重新编译次数,如果编译时间能够证明这一点。

使用预编译头文件和外部头文件保护,这将减少编译器的工作量。

编译器优化

[编辑 | 编辑源代码]

编译器优化 是调整(主要是自动)编译器输出的过程,目的是为了改进程序员请求的操作,从而最大限度地减少或最大限度地提高编译程序的某些属性 _同时确保结果相同_。通过细化编译器优化,程序员可以编写更直观的代码,并使其以合理的速度执行,例如跳过使用 预递增/递减运算符

一般来说,优化没有也不可能在 C++ 标准中定义。该标准制定了一些规则和最佳实践,规定了输入和输出的规范化。C++ 标准本身允许编译器在执行任务时具有一定的自由度,因为某些部分被标记为实现相关的,但通常会建立一个基线,即使如此,一些供应商/实现者也会在一些奇特的特征上进行一些侵入,显然是为了安全性和优化的目的。

需要牢记的一点是,不存在完美的 C++ 编译器,但大多数最新的编译器默认情况下会执行一些简单的优化,这些优化试图抽象并利用现有的更深层的硬件优化或目标平台的特定特征,大多数这些优化几乎总是受欢迎的,但仍然由程序员来决定发生了什么以及它们是否确实有益。因此,强烈建议您检查编译器文档,了解它的工作原理以及哪些优化在程序员的控制之下,仅仅因为编译器理论上可以进行某种优化并不意味着它会进行或它会导致优化。

程序员可以使用最常见的编译器优化选项,这些选项分为三类

  • 速度;提高生成的代码的运行时性能。这是最常见的优化
  • 空间;减小生成的代码的大小
  • 安全;降低数据结构损坏的可能性(例如,确保不会写入非法数组元素)

不幸的是,许多“速度”优化会使代码更大,而许多“空间”优化会使代码更慢,这被称为 时空权衡

自动内联类似于隐式内联。内联可以是优化,也可以是优化,具体取决于代码和所选择的优化选项。

利用扩展指令集

[编辑 | 编辑源代码]
Clipboard

待办事项
3DNow!MMX,等等...


Clipboard

待办事项
添加缺失的信息


运行时

[编辑 | 编辑源代码]

正如我们之前所见,运行时是程序执行的持续时间,从开始到终止。这是所有运行编译代码所需的资源被分配并希望释放的地方,这是任何要执行的程序的最终目标,因此它应该是最终优化的目标。

内存占用

[编辑 | 编辑源代码]

在过去,计算机内存价格昂贵且技术上尺寸有限,并且是程序员的稀缺资源。程序员花费大量精力来实现复杂的程序并使用尽可能少的这种资源来处理大量数据。如今,现代系统包含足够的内存来满足大多数用途,但容量需求和期望也随之增加;因此,最小化内存使用量的技术仍然可能至关重要,事实上,随着移动计算日益重要,操作性能获得了新的动力。

衡量程序的内存使用情况既困难又费时,程序越复杂,获得良好的指标就越困难。问题的另一方面是,除了最基本和通用的考虑之外,没有标准基准(并非所有内存使用量都相同)或实践来解决这个问题。

注意
请注意,在大多数情况下,在调试编译中执行内存测试不会产生有关内存使用的任何有效见解,充其量它可以为您提供所测试函数中预期内存使用上限的指示。

请记住在 std::vector(或 deque)上使用 swap()

当尝试减少(或归零) vectordeque 的大小时,使用 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。

并行化

[编辑 | 编辑源代码]

正如在检查 线程 时所见,它们可以是利用硬件资源并优化程序速度性能的一种“简单”形式。在处理线程时,您应该记住它在复杂性、内存方面存在成本,如果在需要同步时做错了,它甚至会降低速度性能,如果设计允许,最好让线程尽可能不受阻碍地运行。

I/O 读取和写入

[编辑 | 编辑源代码]
队列系统的示意图
Clipboard

待办事项
延迟写入、预读、操作系统如何处理 I/O 请求...


分析是 动态程序分析(与 静态代码分析 相反)的一种形式,它包括使用在程序执行期间收集的信息来研究程序的行为。它的目的是通常确定要优化程序的哪些部分。主要通过确定程序的哪些部分占用了大部分执行时间,导致访问资源的瓶颈或访问这些资源的级别。

在比较应用程序性能时,全局时钟执行时间应该是底线。通过检查执行的渐近阶来选择您的算法,因为在并行设置中,它们将继续提供最佳性能。如果您发现一个无法并行化的热点,即使在检查调用堆栈的更高级别之后也是如此,那么您应该尝试找到一个速度较慢但可并行化的算法。


Clipboard

待办事项
小型示例


分支预测分析器
生成调用图的缓存分析器
逐行分析
堆分析器

分析器

[编辑 | 编辑源代码]
免费分析工具
  • 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 和建模工具,它们可以提高设计质量,并帮助轻松地实现设计模式,从而帮助提高设计质量、自动文档化并缩短开发周期。

UML(统一建模语言)

[编辑 | 编辑源代码]

自 80 年代后期和 90 年代初以来,整个软件工程行业都需要标准化。随着许多新的竞争软件设计方法、概念、符号、术语、流程和与之相关的文化的出现和扩散,统一化的需求因大量平行开发而显而易见。对软件设计表示的共同基础的需求迫切需要,为了存档它,需要对几何图形、颜色和描述进行标准化。

UML(统一建模语言)的创建就是为了服务于此目的,它整合了 Booch(Grady Booch 是 UML 的原始开发人员之一,以其在软件架构、建模和软件工程流程方面的创新工作而闻名)、OMTOOSEClass-RelationOOramand 的概念,将它们融合成一种单一、通用且广泛适用的建模语言,试图成为统一的力量,引入了超越编程语言、操作系统、应用领域和程序员用来描述和交流所需的底层语义的标准符号。它于 1997 年 11 月被 OMG(对象管理组)采纳并得到其支持,已成为行业标准。此后,OMG 呼吁提供有关面向对象方法的信息,这些信息可能会创建一种严格的软件建模语言。许多行业领导者真诚地响应了这一呼吁,帮助创建了该标准,UML 的最后一个版本(v2.0)于 2004 年发布。

UML 仍然被软件行业和工程界广泛使用。在后来的日子里,人们开始意识到(通常被称为 UML 热潮)UML 本身存在局限性,并不是所有工作都适用的工具。需要仔细研究如何以及为什么使用它,才能使其发挥作用。

章节摘要

[编辑 | 编辑源代码]
  1. 资源获取即初始化 (RAII) 开发阶段: 70%
  2. 垃圾回收 (GC) 开发阶段: 80%
  3. 设计模式 开发阶段: 60% - 创建模式结构模式行为模式
  4. 开发阶段: 40% - API 与框架静态和动态库
  5. Boost 库 开发阶段: 50%
  6. 优化您的程序 开发阶段: 60%
  7. 跨平台开发 开发阶段: 40%
    1. Win32(又称 WinAPI) 开发阶段: 30% - 包括 Win32 包装器
    2. 跨平台包装器 开发阶段: 60%
    3. 多任务处理 开发阶段: 70%
  8. 软件国际化 开发阶段: 10%
    1. 文本编码 开发阶段: 10%
  9. 统一建模语言 (UML) 开发阶段: 60%

  1. Malte Skarupke. "测量互斥锁、自旋锁以及 Linux 调度程序到底有多糟糕".
华夏公益教科书