c++设计模式

1.单例模式(Singleton):全局唯一资源的管理者

实例场景:

每个线程一个 EventLoop,主线程要能查到"thread X 的 loop 是哪个";全局配置对象、全局 logger、连接 ID 生成器。

没它会怎样:

把 EventLoop 指针传来传去,每个函数签名都多一个参数;或者滥用全局变量,初始化顺序一团乱(跨编译单元的全局对象初始化顺序是未定义)。

实现(C++ 的现代写法):

c 复制代码
class EventLoopRegistry {
public:
    static EventLoopRegistry& instance() {
        static EventLoopRegistry inst;   // Meyers 单例:C++11 起局部静态变量
        return inst;                     // 初始化是线程安全的(magic statics)
    }
    EventLoop* find(pid_t tid) { /* 查表 */ }

private:
    EventLoopRegistry() = default;              // 私有化构造
    ~EventLoopRegistry() = default;
    EventLoopRegistry(const EventLoopRegistry&) = delete;
    EventLoopRegistry& operator=(const EventLoopRegistry&) = delete;
    std::unordered_map<pid_t, EventLoop*> loops_;
};

工程要点:

  • 别用双检锁(DCLP)那套老写法了,magic statics 语义就是正确的,并发环境多个线程同时调用EventLoopRegistry::instance()编译器会保证有序访问,只产生一个全局实例。
  • 单例的真正问题不是怎么实现,而是该不该用。依据:是否真的是"全局唯一资源"(线程表是,普通业务对象不是);

2.工厂模式(Factory):屏蔽多态对象的创建细节

实际场景:

Poller 多后端------Linux 5.10+ 用 io_uring,老内核用 epoll,mac 开发机用 kqueue,测试环境用 PollPoller。调用方(EventLoop)只想说"给我一个 Poller",不该知道 new 哪个子类。

没它会怎样:

EventLoop 里塞满 #ifdef linux 和版本判断,new EpollPoller / new UringPoller 散落各处,加一个新后端要改 N 个文件。

实现(简单工厂 + 注册表,这是 STL 风格的现代写法):

c 复制代码
// 一、被创建对象的抽象基类:Poller
class EventLoop;  // 前向声明,本例中仅占位
class Poller {
};

// 二、注册表:名字 -> 构造函数(工厂函数)
class PollerFactoryRegistry {
public:
    // 工厂函数签名:只依赖 EventLoop*,返回独占所有权
    using FactoryFn = std::function<std::unique_ptr<Poller>(EventLoop*)>;
    // magic statics(见"单例"一节):线程安全、惰性初始化
    static PollerFactoryRegistry& instance() {
        static PollerFactoryRegistry r;
        return r;
    }
    // 注册发生在 main 之前的静态初始化阶段,天然单线程,无需加锁
    void add(const std::string& name, FactoryFn fn) {
        if (!factories_.emplace(name, std::move(fn)).second) {
            std::fprintf(stderr, "PollerFactory: duplicate registration '%s'\n", name.c_str());
            std::abort();  // 重复注册是编码错误,直接暴露
        }
    }
    // 按名创建 ------ 名字不对是配置错误,抛异常让启动失败(fail fast)
    std::unique_ptr<Poller> create(const std::string& name, EventLoop* loop) const {
        auto it = factories_.find(name);
        if (it == factories_.end()) {
            ,,,
        }
        return it->second(loop);
    }
private:
    std::map<std::string, FactoryFn> factories_;
};
struct PollerRegistrar {
    PollerRegistrar(const char* name, PollerFactoryRegistry::FactoryFn fn) {
        PollerFactoryRegistry::instance().add(name, std::move(fn));
    }
};

// 三、具体后端 1:EpollPoller(模拟独立编译单元 epoll_poller.cc)
class EpollPoller : public Poller {
};
namespace {
PollerRegistrar _reg_epoll("epoll", [](EventLoop* l) {
    return std::make_unique<EpollPoller>(l);
});
}  // namespace

// 四、具体后端 2:PollPoller(模拟 poll_poller.cc)
class PollPoller : public Poller {
};
namespace {
PollerRegistrar _reg_poll("poll", [](EventLoop* l) {
    return std::make_unique<PollPoller>(l);
});
}  // namespace

// 六、main:调用方完全不知道具体后端的存在
int main() {
		EventLoop loop;
    // 工厂入口
    std::unique_ptr<Poller> poller = PollerFactoryRegistry::instance().create("poll", &loop);  
    return 0;
}

工程要点:

  • 先注册到工厂对象;再通过工厂接口实现特定类别注册对象的获取;
  • 返回值用 std::unique_ptr------所有权语义的自我声明,工厂不保留、不共享。
  • 注意上述用std::unique_ptr<Driver>移动构造出一个 std::unique_ptr<Base>的用法。

3.观察者模式(Observer):一对多的事件通知

实际场景:

TCP 连接的连接建立/数据到达/断开/错误事件;配置中心推送到所有订阅者(你的分布式系统里一定有 config watch 的需求)。

没它会怎样:

一个连接一个 onMessage 回调函数,多个模块都关心怎么办?链式调用硬编码,新增订阅者改框架代码------不可接受。

实现(C++ 现代观察者 = std::function + weak_ptr):

c 复制代码
class Connection {
public:
    using MessageCallback = std::function<void(const ConnectionPtr&, Buffer&)>;
    using CloseCallback   = std::function<void(const ConnectionPtr&)>;
    // 允许挂多个观察者(注意:生产代码通常只有一个 onMessage,这里是展示多观察者)
    void addMessageHandler(std::weak_ptr<void> owner, MessageCallback cb) {
        handlers_.push_back({std::move(owner), std::move(cb)});
    }
    void onMessage() {
        for (auto it = handlers_.begin(); it != handlers_.end(); ) {
            if (auto owner = it->owner.lock()) {        // 观察者还活着吗?
                it->cb(shared_from_this(), inputBuffer_);
                ++it;
            } else {
                it = handlers_.erase(it);               // 自动清理死掉的观察者
            }
        }
    }
private:
    struct Handler { std::weak_ptr<void> owner; MessageCallback cb; };
    std::vector<Handler> handlers_;
};

工程要点:

  • weak_ptr 防悬空是观察者的头号生存法则。

4.策略模式(Strategy):运行时可替换的算法

实际场景:

负载均衡(轮询/随机/一致性哈希/最少连接);RPC 的重试策略(不重试/固定间隔/指数退避+jitter);超时策略。

没它会怎样:

一个 chooseNode() 函数里 switch-case 四种算法,每个 RPC 框架都这样写,最后长成 800 行的上帝函数。

实现(C++ 有两条路,都很有代表性):

  • 路线一:运行时多态(算法数量少、需要配置切换时用)
c 复制代码
class LoadBalancer {
public:
    virtual ~LoadBalancer() = default;
    virtual const Node& select(const std::vector<Node>& nodes, uint64_t key) = 0;
};

class RoundRobin : public LoadBalancer {
public:
    const Node& select(const std::vector<Node>& nodes, uint64_t) override {
    }
};

class ConsistentHash : public LoadBalancer {
public:
    const Node& select(const std::vector<Node>&, uint64_t key) override {
    }
};

// 配置驱动:"lb.strategy = consistent_hash" → 工厂创建
// 利用工厂取得具体的策略对象
std::unique_ptr<LoadBalancer> lb = LoadBalancerFactory::create(config);
// 通过基类指针调用虚方法完成策略使用
lb->select(...);
相关推荐
旖旎夜光2 小时前
LeetCode 315: 算右侧小于当前元素的个数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
hiahiahia1232 小时前
Streaming Model Output
开发语言·前端
工一木子2 小时前
设计模式与生活⑦:你打的车,隔着一个平台
设计模式
D e re k ZH2 小时前
保序回归:从 PAVA 到可并堆的单调约束优化
c++·算法·数据挖掘·回归·kotlin
青少儿编程课堂2 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:字典树前缀树与单词检索统计
c++·python·算法·bfs·信息学竞赛
熊猫钓鱼>_>2 小时前
鸿蒙PC高分屏与键鼠交互适配:DPI缩放、右键菜单一次讲清
开发语言·笔记·华为·ai编程·harmonyos·鸿蒙·鸿蒙心迹
电子科技圈2 小时前
2026 RISC-V中国峰会观察:编译调试工具链的“在场节奏”,释放了什么信号?
大数据·人工智能·设计模式·软件工程·软件构建·代码规范·risc-v
octopus_c3 小时前
C++ 运算符重载详解 && 日期类的实现
开发语言·c++
青少儿编程课堂3 小时前
2026蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:堆排序与优先队列解析
c++·python·算法·bfs·信息学竞赛