/Design-Patterns-in-Cpp17

基于 C++17 实现 23 种 GoF 设计模式,使用智能指针来避免内存泄漏。Implement 23 GoF design patterns in C++17, make proper use of smart pointers to avoid memory leak.

Primary LanguageC++MIT LicenseMIT

Supported Platforms Build Status GitHub license

前言

class B;

class A {
 public:
  std::shared_ptr<B> b;
};

class B {
 public:
  std::shared_ptr<A> a;
};

int main() {
  {
    auto x = std::make_shared<A>();
    x->b = std::make_shared<B>();
    x->b->a = x;
  }  // x 的引用计数由 2 减为 1,不会析构,于是 x->b
     // 也不会析构,导致两次内存泄漏
  // 解决方案是将 B::a 改为 std::weak_ptr,这样引用计数不会为2,而是保持
  // 1,此处就会由 1 减为 0,从而正常析构
}

检测内存泄漏的方法

#include <crtdbg.h>

#ifdef _DEBUG
#define new new (_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int main() {
  int* p = new int(42);
  _CrtDumpMemoryLeaks();
}
  • 调试时将提示第 9 行产生 4 字节的内存泄漏
Detected memory leaks!
Dumping objects ->
xxx.cpp(9) : {88} normal block at 0x008C92D0, 4 bytes long.
 Data: <*   > 2A 00 00 00 
Object dump complete.
  • 这种方法的原理是,在执行此函数时,检查所有未回收的内存,因此存在析构函数还未执行而误报的情况
#include <crtdbg.h>

#include <memory>

#ifdef _DEBUG
#define new new (_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int main() {
  auto p = std::make_shared<int>(42);
  _CrtDumpMemoryLeaks();  // 此时 std::shared_ptr 还未析构,因此报告内存泄漏
}
#include <crtdbg.h>

#ifdef _DEBUG
#define new new (_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int main() {
  _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
  int* p = new int(42);
}
  • 该项目中的源码使用上述方式检测均无内存泄漏
  • 为了尽可能简单,所有代码均以单个源文件的形式实现
  • 源码中未使用平台相关 API,因此在任何支持 C++17 标准的平台均可通过编译

智能指针的传参方式选择

  • 以下两种传参方式,应该如何选择?
void f(std::shared_ptr<A> p);         // 传值
void f(const std::shared_ptr<A>& p);  // 传引用
  • 通常情况下,采用传引用方式是最稳妥且没有心智负担的
  • 传值方式只在之后一定会对其拷贝时才有使用的可能
void f(std::shared_ptr<A> p) {
  auto q = std::move(p);
  // ...
}
  • 这种情况常见于构造函数中
class X {
 public:
  explicit X(std::shared_ptr<A> _p) : p(std::move(_p)) {}

 private:
  std::shared_ptr<A> p;
};
  • 不同的传参方式表达了不同的语义
void f(A&);  // 仅使用对象,不涉及对象资源所有权的管理
void f(A*);  // 仅使用对象,不涉及对象资源所有权的管理
void f(std::unique_ptr<A>);  // 用于转移唯一所有权(用 std::move 传入)
void f(std::unique_ptr<A>&);        // 用于重置内部对象
void f(const std::unique_ptr<A>&);  // 不如直接传引用或原始指针
void f(std::shared_ptr<A>);  // 引用计数共享,可接受 std::unique_ptr 实参(用
                             // std::move 传入)
void f(std::shared_ptr<A>&);  // 引用计数不变,用于重置内部对象,不可接受
                              // std::unique_ptr 实参
void f(const std::shared_ptr<A>&);  // 引用计数不变,不可重置内部对象,可接受
                                    // std::unique_ptr 实参(用 std::move 传入)

设计模式简介

  • 设计模式的概念最初源自建筑行业,建筑师 Christopher Alexander 曾这样解释过模式的概念:“总会有一类问题在我们身边反复出现,模式就是针对这一类问题的通用解法。当问题反复出现时,直接套用这个解法即可,而不需要去重新解决问题。”

Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.

  • 后来,模式的**也被引入了软件工程领域,软件工程中提出了以下设计原则,设计模式也遵循了这些原则
    • 开闭原则(The Open-Closed Principle,OCP):对扩展开放,对修改关闭。修改程序时,不需要修改类内部代码就可以扩展类的功能,如装饰器模式
    • Liskov 替换原则(Liskov Substitution Principle,LSP):任何基类出现的地方,都可以用派生类替换
    • 依赖倒置原则(Dependency Inversion Principle,DIP):针对接口(纯虚函数)编程,而非针对实现编程
    • 接口分离原则(Interface Segregation Principle,ISP):接口功能的粒度应该尽可能小,多个功能分离的小接口、单个合并了这些功能的大接口,前者是更好的做法,因为这样降低了依赖,耦合度更低
    • 发布复用等价性原则(Release Reuse Equivalency Principle,REP):复用的粒度就是发布的粒度。第三方库的作者需要维护每个版本,作者可以修改代码,但是用户不需要了解源码的变化,而可以自由选择使用哪个版本的库,因此库作者应该将可复用的类打包成包,以包为单位来更新,而不是更新每个类,比如 Boost 有一个版本号,而其中的每个子部分(如 Boost.Asio)又有各自独立的版本号
    • 共同封装原则(Common Closure Principle,CCP):一同变更的类应该合在一起,如果一些类处理相同的功能或行为域,那么这些类应该根据内聚性分到一组打包,这样需要修改某个域的功能时,只需要修改这个包中的代码
    • 共同复用原则(Common Reuse Principle,CRP):不能一起被复用的类不能被分到一组。当包中的类变化时,包的版本号也会变化,如果不相关的类被分到一组,就会导致本来无必要的包的版本升级,为此又需要进行本来无必要的集成和测试
  • 设计模式是从已有的软件设计中,针对重复出现的问题提取出的一套经验论,其概念源自 Design Patterns: Elements of Reusable Object-Oriented Software,此书由四人合著,因此简称 GoF(Gang of Four)。GoF 总结归纳了 23 种设计模式,分为创建型(Creational)、结构型(Structural)、行为型(Behavioral)三类。一些设计模式已经用在编程语言或框架中(如 C#QtRxJS)。设计模式减少了术语交流的沟通成本,灵活使用它来写出低耦合少重复的代码。
创建型模式 中文名 说明 实现
Abstract Factory/Kit 抽象工厂模式 README C++
Builder 建造者模式 README C++
Factory Method/Virutal Contructor 工厂方法模式 README C++
Prototype 原型模式 README C++
Singleton 单例模式 README C++
结构型模式 中文名 说明 实现
Adapter/Wrapper 适配器模式 README C++
Bridge/Handle/Body 桥接模式 README C++
Composite 组合模式 README C++
Decorator/Wrapper 装饰器模式 README C++
Facade 外观模式 README C++
Flyweight 享元模式 README C++
Proxy/Surrogate 代理模式 README C++
行为型模式 中文名 说明 实现
Chain of Responsibility 责任链模式 README C++
Command/Action/Transaction 命令模式 README C++
Interpreter 解释器模式 README C++
Iterator/Cursor 迭代器模式 README C++
Mediator 中介者模式 README C++
Memento/Token 备忘录模式 README C++
Observer/Dependents/Publish-Subscribe 观察者模式 README C++
State/Objects for States 状态模式 README C++
Strategy/Policy 策略模式 README C++
Template Method 模板方法模式 README C++
Visitor 访问者模式 README C++