Skip to content

设计模式

单例模式(Singleton)

单例模式确保:

  1. 一个类只有一个实例
  2. 全局可访问这个实例

典型用途:日志系统、配置管理器、数据库连接池、线程池等。

“懒汉式”与“饿汉式”的区别

饿汉式: 程序启动时就创建实例, 简单、线程安全(初始化时机唯一)可能浪费资源

懒汉式: 第一次使用时才创建实例,延迟加载、节省资源,需要考虑线程安全

Meyers' Singleton

class Singleton {
private:
    Singleton() {}                     // 私有构造
    ~Singleton() {}                    // 可选:防止外部析构
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
public:
    static Singleton& getInstance() {
        static Singleton instance;     // C++11保证线程安全初始化
        return instance;
    }
};

问题

  1. 创建singleton实例可能会失败,这个时候怎么保证异常安全[solved]
  2. 用new+atexit创建还是用uniqueptr[solved]
  3. 多线程访问的安全, 什么时候变量要加atomic
  4. 静态析构的顺序会有什么问题[solved]
  5. callonce什么时候用[solved]
  6. 怎么证明一个类是线程安全的
  7. 什么时候要用到更精细地控制销毁时机[solved]
  8. 什么时候出现多个单例[solved]
  9. C++ 运行时库是什么

什么时候要用到更精细地控制销毁时机

C++ 程序结束时,静态变量的析构是由 C++ 运行时库 (CRT, C Runtime Library) 自动管理的。核心机制可以总结为:“登记制” + “后进先出 (LIFO)”

这段代码会调用 C 语言标准库的一个内部函数(通常是 std::atexit 或编译器特定的 __cxa_atexit),将该对象的析构函数注册到一个全局的函数指针栈中。

  • 场景:假设你有两个单例:Logger(日志器)和 Database(数据库)。但在 Database 的析构函数中,可能需要写一条日志:“数据库连接已关闭”。如果此时 Logger 已经被销毁了,程序就会崩溃或发生未定义行为。
  • 你的单例管理着巨大的内存块(例如 1GB 的纹理缓存)如果这些资源只在程序的某个阶段(比如“游戏关卡加载”或“初始化阶段”)使用,而程序后续还需要运行很长时间。如果不手动销毁,这些资源会一直被占用直到程序彻底退出
  • 单例定义在一个动态链接库(DLL / .so)中,主程序会动态加载和卸载这个库。如果单例对象是静态局部变量,它的析构函数通常注册在 atexit 中,试图在主程序退出时执行。但此时插件的代码内存早已不存在,调用析构函数会导致 Crash。

返回Reference 和返回指针的区别

reference不能表示空,指针可以表示空,允许构造失败。不能使用 std::call_once(因为它一旦执行完就会标记为“已完成”,哪怕你吞掉了异常)。

我们需要使用 Double-Checked Locking (DCL) 配合 try-catch,这样如果初始化失败,下次调用还能再次尝试初始化。

手动控制销毁:不利用 atexit 自动销毁,而是提供一个静态的 destroy() 方法,让你在 main 函数结束前按你想要的顺序手动“点名”销毁。

意外多例

场景:头文件里写了函数内静态(Meyers Singleton),但被编进多个动态库. 如果 可执行文件某个 DLL/.so 都各自编译并链接了这段 instance(),并且符号/链接方式导致它们各自保留一份静态变量,那么:

  • EXE 里有一个 S
  • DLL 里也有一个 S 你在不同模块里调用 instance(),拿到的不是同一个对象。

实现

#include <expected>
#include <memory>
#include <mutex>
#include <string>
#include <utility>

struct InitError {
  int code = 0;
  std::string message;
};

// 你的单例类型
class Service {
public:
  // 对外:获取单例(可能失败)
  static std::expected<Service&, InitError> get() {
    std::call_once(s_once, [] {
      s_result = create(); // 只执行一次:成功 or 失败都会被缓存
    });

    if (!s_result.has_value()) {
      return std::unexpected(s_result.error());
    }
    return *s_result.value();
  }

  // 示例接口
  int doWork() const { return 42; }

  Service(const Service&) = delete;
  Service& operator=(const Service&) = delete;

private:
  Service() = default;

  // 初始化可能失败的创建逻辑
  static std::expected<std::unique_ptr<Service>, InitError> create() {
    // 这里写真实初始化:打开文件/连接数据库/创建GPU资源等
    bool ok = false; // 示例:让它失败
    if (!ok) {
      return std::unexpected(InitError{1, "Failed to initialize Service (demo)"});
    }

    auto p = std::unique_ptr<Service>(new Service());
    // 继续做更多初始化,失败就 return unexpected(...)
    return p;
  }

private:
  inline static std::once_flag s_once;
  inline static std::expected<std::unique_ptr<Service>, InitError> s_result =
      std::unexpected(InitError{0, "Not initialized yet"}); // 初始占位
};

// 用法示例
// auto r = Service::get();
// if (!r) { printf("err: %d %s\n", r.error().code, r.error().message.c_str()); }
// else { printf("%d\n", r->doWork()); }

实现

#include <memory>
#include <mutex>
#include <string>

class Service {
public:
  static Service* get(std::string* err = nullptr) {
    std::call_once(s_once, [] {
      s_instance = create(s_error);
      s_inited = true; // 表示“已经尝试过初始化”(无论成败)
    });

    if (!s_instance && err) *err = s_error;
    return s_instance.get();
  }

  int doWork() const { return 42; }

  Service(const Service&) = delete;
  Service& operator=(const Service&) = delete;

private:
  Service() = default;

  static std::unique_ptr<Service> create(std::string& err) {
    bool ok = false; // 示例:让它失败
    if (!ok) {
      err = "Failed to initialize Service (demo)";
      return nullptr;
    }
    return std::unique_ptr<Service>(new Service());
  }

private:
  inline static std::once_flag s_once;
  inline static std::unique_ptr<Service> s_instance{};
  inline static std::string s_error{};
  inline static bool s_inited = false; // 可选:用于调试/诊断
};

Factory Method工厂模式

业务代码只依赖抽象:AbstractFactory + AbstractProduct

当你有多种产品,而且这些产品是成套出现(产品族)的:

  • 例子:UI 组件一套:Button / TextBox / CheckBox
  • 你有不同风格(产品族):Windows 风格一套Mac 风格一套
  • 业务代码希望:换主题/平台时,一次性换整套组件,并且保证组件之间“配套”。

如果不用抽象工厂,你可能会在代码里到处写:

if (platform == Windows) new WinButton();
else new MacButton();

这会导致业务代码到处都是分支、很难维护。

#include <iostream>
#include <memory>
#include <string>

// ====== Abstract Products ======
struct Button {
  virtual ~Button() = default;
  virtual void paint() const = 0;
};

struct TextBox {
  virtual ~TextBox() = default;
  virtual void paint() const = 0;
};

// ====== Concrete Products: Windows ======
struct WinButton : Button {
  void paint() const override { std::cout << "[Win] Button\n"; }
};

struct WinTextBox : TextBox {
  void paint() const override { std::cout << "[Win] TextBox\n"; }
};

// ====== Concrete Products: Mac ======
struct MacButton : Button {
  void paint() const override { std::cout << "[Mac] Button\n"; }
};

struct MacTextBox : TextBox {
  void paint() const override { std::cout << "[Mac] TextBox\n"; }
};

// ====== Abstract Factory ======
struct GUIFactory {
  virtual ~GUIFactory() = default;
  virtual std::unique_ptr<Button>  createButton() const = 0;
  virtual std::unique_ptr<TextBox> createTextBox() const = 0;
};

// ====== Concrete Factories ======
struct WinFactory : GUIFactory {
  std::unique_ptr<Button> createButton() const override {
    return std::make_unique<WinButton>();
  }
  std::unique_ptr<TextBox> createTextBox() const override {
    return std::make_unique<WinTextBox>();
  }
};

struct MacFactory : GUIFactory {
  std::unique_ptr<Button> createButton() const override {
    return std::make_unique<MacButton>();
  }
  std::unique_ptr<TextBox> createTextBox() const override {
    return std::make_unique<MacTextBox>();
  }
};

// ====== Client Code ======
void renderUI(const GUIFactory& factory) {
  auto btn = factory.createButton();
  auto tb  = factory.createTextBox();

  // 客户端完全不关心具体类型,只用抽象接口
  btn->paint();
  tb->paint();
}

int main(int argc, char** argv) {
  // 运行时决定使用哪个产品族(Windows or Mac)
  std::unique_ptr<GUIFactory> factory;

  std::string platform = (argc > 1 ? argv[1] : "win");
  if (platform == "mac") {
    factory = std::make_unique<MacFactory>();
  } else {
    factory = std::make_unique<WinFactory>();
  }

  renderUI(*factory);
  return 0;
}

建造者模式Builder

#include <string>
#include <map>
#include <optional>
#include <stdexcept>

struct HttpRequest {
  std::string url;
  std::string method = "GET";
  std::map<std::string, std::string> headers;
  std::string body;
  int timeout_ms = 3000;
};

class HttpRequestBuilder {
public:
  explicit HttpRequestBuilder(std::string url) : req_{std::move(url)} {}

  HttpRequestBuilder& method(std::string m) {
    req_.method = std::move(m);
    return *this;
  }

  HttpRequestBuilder& header(std::string k, std::string v) {
    req_.headers.emplace(std::move(k), std::move(v));
    return *this;
  }

  HttpRequestBuilder& body(std::string b) {
    req_.body = std::move(b);
    return *this;
  }

  HttpRequestBuilder& timeoutMs(int t) {
    req_.timeout_ms = t;
    return *this;
  }

  HttpRequest build() const {
    // 统一校验点:必填字段、组合约束等
    if (req_.url.empty()) throw std::invalid_argument("url is empty");
    return req_;
  }

private:
  HttpRequest req_;
};
// 用法:
// auto req = HttpRequestBuilder("https://api.xxx.com")
//              .method("POST")
//              .header("Content-Type","application/json")
//              .body("{...}")
//              .timeoutMs(5000)
//              .build();

原型模式

1)跳过昂贵初始化

复杂对象的“创建”往往不只是 new

  • 解析大配置 / JSON / XML
  • 构建复杂数据结构(大 vector、索引、哈希表、图结构)
  • 预计算缓存、生成查找表
  • 初始化一堆子对象、做一致性校验

这些步骤通常是 O(n) 甚至更高,而且包含大量逻辑分支

适用场景(常见面试点):

  • 创建对象成本很高(初始化复杂、要读配置/解析、要建大量内部结构)
  • 你想要很多“差不多”的对象,只是少量字段不同
  • 希望创建过程与具体类解耦:客户端只拿到一个基类指针,通过 clone() 得到新对象

一句话:

先造一个“模板对象”,之后都从它复制,再做少量修改。

桥接模式

如果你有两个维度都在变化:

  • 维度 A:抽象层(比如“形状:圆/矩形/三角形”)
  • 维度 B:实现层(比如“渲染方式:OpenGL/DirectX/Vulkan”)

不用桥接时,常见写法是继承组合:

  • OpenGLCircle, OpenGLRectangle
  • DirectXCircle, DirectXRectangle
  • ……

这会导致类数量 = A × B,扩展任何一边都要新增一堆类。

桥接用组合替代这种继承笛卡尔积:

  • 抽象层(Shape)持有一个实现层接口(Renderer*)
  • 形状新增不影响渲染器新增,反之亦然

Strategy

#include <iostream>
#include <functional>

class Cashier {
public:
  using Strategy = std::function<double(double)>;

  explicit Cashier(Strategy s) : strategy_(std::move(s)) {}

  void setStrategy(Strategy s) { strategy_ = std::move(s); }

  double checkout(double base) const { return strategy_(base); }

private:
  Strategy strategy_;
};

int main() {
  Cashier c([](double x){ return x; });
  std::cout << c.checkout(100) << "\n"; // 100

  c.setStrategy([](double x){ return x * 0.9; });
  std::cout << c.checkout(100) << "\n"; // 90
}

装饰(Decorator)模式

装饰(Decorator)模式:在不改原类代码、也不靠继承爆炸的情况下,给对象动态叠加功能。它的关键词是:包装(wrap) + 叠加(stackable) + 同接口(is-a)

#include <iostream>
#include <memory>
#include <string>

// ===== Component =====
struct Beverage {
  virtual ~Beverage() = default;
  virtual std::string desc() const = 0;
  virtual double cost() const = 0;
};

// ===== ConcreteComponent =====
struct Espresso : Beverage {
  std::string desc() const override { return "Espresso"; }
  double cost() const override { return 12.0; }
};

// ===== Decorator Base =====
class CondimentDecorator : public Beverage {
public:
  explicit CondimentDecorator(std::unique_ptr<Beverage> inner)
      : inner_(std::move(inner)) {}

protected:
  std::unique_ptr<Beverage> inner_;
};

// ===== ConcreteDecorators =====
class Milk : public CondimentDecorator {
public:
  using CondimentDecorator::CondimentDecorator;

  std::string desc() const override { return inner_->desc() + " + Milk"; }
  double cost() const override { return inner_->cost() + 2.0; }
};

class Sugar : public CondimentDecorator {
public:
  using CondimentDecorator::CondimentDecorator;

  std::string desc() const override { return inner_->desc() + " + Sugar"; }
  double cost() const override { return inner_->cost() + 1.0; }
};

int main() {
  std::unique_ptr<Beverage> drink = std::make_unique<Espresso>();
  drink = std::make_unique<Milk>(std::move(drink));
  drink = std::make_unique<Sugar>(std::move(drink));
  drink = std::make_unique<Sugar>(std::move(drink)); // 还能再叠一层

  std::cout << drink->desc() << "\n";
  std::cout << "Cost: " << drink->cost() << "\n";
}

Observer

场景:一对多通知:GUI 事件、订阅/发布、配置更新、游戏事件等。

class Observer {
public:
    virtual ~Observer() = default;
    virtual void onNotify(int data) = 0;
};

class Subject {
public:
    void addObserver(Observer* o) { obs_.push_back(o); }
    void notify(int data) {
        for (auto o : obs_) o->onNotify(data);
    }
private:
    std::vector<Observer*> obs_;
};

Reactor pattern

在有成千上万连接时,避免“一个连接一个线程”的资源浪费,用事件驱动 + 非阻塞 IO来处理大量并发连接。

Proactor Pattern

Proxy Pattern

代理模式(Proxy Pattern):为某个对象提供一个替身/中介(代理),由代理控制对真实对象的访问。对外接口不变,调用方“看起来”在用目标对象,实际上请求会先经过代理