You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++14支持泛型容器的灵活观察者事件系统实现难题

问题:C++14观察者模式事件系统的容器接口泛化设计

我正在基于C++14实现一个支持观察者模式的事件系统,要求能灵活切换vector、map等不同容器管理观察者。当前系统包含Observer类、负责挂载/卸载观察者的Subject类,以及继承自Subject的EventSystem类,目标是让EventSystem可以自定义使用VectorContainer、MapContainer等容器类型。

核心问题是:希望泛化EventSystem中的容器类型,使其同时兼容单元素容器(如VectorContainer)和键值对容器(如MapContainer<Key, Value>),但现有IContainer接口仅支持单个模板参数,无法适配带双参数的MapContainer,难以设计出能同时处理单个元素与键值对的统一容器接口。

现有代码结构如下:

#include <functional>
#include <unordered_map>
#include <iostream>
#include <type_traits>

template<typename MessageType>
class Observer
{
public:
    virtual ~Observer() = default;
    
    void Handle(const MessageType& message) { ApplyHandle(message); }
private:

    virtual void ApplyHandle(const MessageType& message) = 0;
};

template<typename MessageType, typename ObserverType, typename observerId>
class Subject 
{
public:
    virtual ~Subject() = default;
    virtual void Attach(ObserverType& observer, const observerId& observerID) = 0;
    virtual void Detach(const observerId& observerID) = 0;
};

template<typename MessageType, typename ObserverType, typename observerId, typename ContainerType>
class EventSystem : public Subject<MessageType, ObserverType, observerId>
{
    public:

    ~EventSystem() = default;
    
    
    void Attach(const observerId& observerID, ObserverType& observer) override
    {
        mContainer.Add(observerID, observer);
    }
    
    void Detach(const observerId& observerID) override
    {
        mContainer.Remove(observerID);
    }
    
    private:
    ContainerType mContainer;
};

template <typename T>
class IContainer
{
public:
    virtual void Add(const T& item) = 0;     // Adds item
    virtual void Remove(const T& item) = 0;  // Remove the item if it exists in the container
    virtual ~IContainer() = default;
};

template <typename T, typename ContainerType>
class Container : public IContainer<T>
{
public:
    void Add(const T& item) override
    {
        container.Add(item);
    }

    void Remove(const T& item) override
    {
        container.Remove(item);
    }

private:
    ContainerType container;
};

template <typename T>
class VectorContainer
{
public:
    void Add(const T& item)
    {
        vect.push_back(item);
    }

    void Remove(const T& item)
    {
        // Assuming T has a valid equality comparison
        auto it = std::remove(vect.begin(), vect.end(), item);
        vect.erase(it, vect.end());
    }

private:
    std::vector<T> vect;
};

template <typename Key, typename Value>
class MapContainer
{
public:
    void Add(const std::pair<const Key, Value>& keyValue)
    {
        container.insert(keyValue);
    }

    void Add(const Key& key, const Value& value) 
    {
        container[key] = value;
    }

    void Remove(const std::pair<const Key, Value>& keyValue)
    {
        container.erase(keyValue.first);
    }

private:
    std::map<Key, Value> container;
};

解决方案1:直接适配容器方法签名

核心思路是放弃强制统一的单参数容器接口,让容器直接实现EventSystem所需的方法契约(Add(observerId, observer)和Remove(observerId)),通过适配不同容器的调用逻辑来兼容两类容器。

修改后的关键代码:

1. 适配VectorContainer,添加支持键值对的方法

template <typename T>
class VectorContainer
{
public:
    // 保留原有单元素添加接口
    void Add(const T& item)
    {
        vect.push_back(item);
    }

    // 适配EventSystem的Attach调用:将ID和观察者打包为pair存储
    void Add(const auto& observerId, T& observer)
    {
        vect.push_back(std::make_pair(observerId, std::ref(observer)));
    }

    // 保留原有单元素移除接口
    void Remove(const T& item)
    {
        auto it = std::remove(vect.begin(), vect.end(), item);
        vect.erase(it, vect.end());
    }

    // 适配EventSystem的Detach调用:按ID查找并移除
    void Remove(const auto& observerId)
    {
        auto it = std::remove_if(vect.begin(), vect.end(),
            [&observerId](const auto& pair) { return pair.first == observerId; });
        vect.erase(it, vect.end());
    }

    // 提供遍历接口供事件触发使用
    auto begin() { return vect.begin(); }
    auto end() { return vect.end(); }

private:
    std::vector<std::pair<decltype(std::declval<observerId>()), std::reference_wrapper<T>>> vect;
};

2. 调整MapContainer,优化观察者存储方式

template <typename Key, typename Value>
class MapContainer
{
public:
    void Add(const std::pair<const Key, Value>& keyValue)
    {
        container.insert(keyValue);
    }

    // 直接适配EventSystem的Attach调用
    void Add(const Key& key, Value& value) 
    {
        container[key] = std::ref(value); // 存储引用避免拷贝
    }

    // 适配EventSystem的Detach调用
    void Remove(const Key& key)
    {
        container.erase(key);
    }

    void Remove(const std::pair<const Key, Value>& keyValue)
    {
        container.erase(keyValue.first);
    }

    // 提供遍历接口
    auto begin() { return container.begin(); }
    auto end() { return container.end(); }

private:
    std::map<Key, std::reference_wrapper<Value>> container;
};

3. 简化EventSystem,直接依赖容器契约

template<typename MessageType, typename ObserverType, typename observerId, typename ContainerType>
class EventSystem : public Subject<MessageType, ObserverType, observerId>
{
public:
    ~EventSystem() = default;
    
    void Attach(const observerId& observerID, ObserverType& observer) override
    {
        mContainer.Add(observerID, observer);
    }
    
    void Detach(const observerId& observerID) override
    {
        mContainer.Remove(observerID);
    }

    // 事件触发实现
    void Trigger(const MessageType& message)
    {
        for (auto& entry : mContainer)
        {
            // 统一处理vector的pair和map的entry
            auto& observer = std::is_same<decltype(entry), std::pair<observerId, std::reference_wrapper<ObserverType>>>::value
                ? entry.second
                : entry.second;
            observer.get().Handle(message);
        }
    }
    
private:
    ContainerType mContainer;
};

解决方案2:CRTP静态多态接口

如果需要保留接口抽象且避免虚函数开销,可以使用**奇异递归模板模式(CRTP)**实现静态多态,让不同容器继承统一的模板接口:

关键代码:

// CRTP基类,定义容器接口契约
template<typename Derived>
class ContainerInterface
{
public:
    template<typename... Args>
    void Add(Args&&... args)
    {
        static_cast<Derived*>(this)->Add(std::forward<Args>(args)...);
    }

    template<typename... Args>
    void Remove(Args&&... args)
    {
        static_cast<Derived*>(this)->Remove(std::forward<Args>(args)...);
    }

    auto begin() { return static_cast<Derived*>(this)->begin(); }
    auto end() { return static_cast<Derived*>(this)->end(); }
};

// VectorContainer继承CRTP接口
template <typename T>
class VectorContainer : public ContainerInterface<VectorContainer<T>>
{
public:
    void Add(const T& item) { /* 原有实现 */ }
    void Add(const auto& id, T& observer) { /* 适配实现 */ }
    void Remove(const T& item) { /* 原有实现 */ }
    void Remove(const auto& id) { /* 适配实现 */ }
    auto begin() { return vect.begin(); }
    auto end() { return vect.end(); }

private:
    std::vector<std::pair<decltype(std::declval<observerId>()), std::reference_wrapper<T>>> vect;
};

// MapContainer继承CRTP接口
template <typename Key, typename Value>
class MapContainer : public ContainerInterface<MapContainer<Key, Value>>
{
public:
    void Add(const Key& key, Value& value) { /* 原有实现 */ }
    void Remove(const Key& key) { /* 原有实现 */ }
    auto begin() { return container.begin(); }
    auto end() { return container.end(); }

private:
    std::map<Key, std::reference_wrapper<Value>> container;
};

// EventSystem使用CRTP接口
template<typename MessageType, typename ObserverType, typename observerId, typename ContainerType>
class EventSystem : public Subject<MessageType, ObserverType, observerId>
{
public:
    void Attach(const observerId& observerID, ObserverType& observer) override
    {
        mContainer.Add(observerID, observer);
    }
    
    void Detach(const observerId& observerID) override
    {
        mContainer.Remove(observerID);
    }

    void Trigger(const MessageType& message)
    {
        for (auto& entry : mContainer)
        {
            auto& observer = entry.second;
            observer.get().Handle(message);
        }
    }
    
private:
    ContainerType mContainer;
};

关键注意事项

  • 存储观察者时建议使用std::reference_wrapper,避免拷贝观察者对象,同时需确保观察者生命周期长于事件系统
  • 方案1更简洁,适合快速迭代;方案2通过CRTP实现静态多态,无运行时虚函数开销,适合性能敏感场景
  • 两种方案均无需强制统一容器的参数数量,而是通过对齐EventSystem的方法契约实现兼容

内容的提问来源于stack exchange,提问作者Fatih Sevencan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:58:15