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

C++用类实例替换枚举:typeid、堆分配及替代方案疑问

用类实例替代枚举的设计疑问与优化方案

我希望用类实例替代如下枚举:

enum{ eTagA, eTagB,...}

这样无需修改原枚举即可新增标签。我编写了如下代码:

#include <memory>
#include <list>

struct TagBase {
    bool operator== (const TagBase& other) const {
        return typeid(*this) == typeid(other);
    }

    bool operator!= (const TagBase& other) const {
        return !(*this == other);
    }
    
    virtual ~TagBase() = default;
};

bool operator== (const std::unique_ptr<TagBase>& lhs, const TagBase& base) {
    return base == *(lhs.get());
}

class Namespace {
public:
    template<typename T>
    void AddTag() {
        tags.push_back(std::unique_ptr<T>(new T()));
    }
    
    bool HasTag(const TagBase& tag) const {
        return std::find(std::begin(tags), std::end(tags), tag) != tags.end();
    }

private:
    // using of std::unique_ptr is the only way?
    std::list<std::unique_ptr<TagBase>> tags;
};

struct TagA: public TagBase {};
struct TagB: public TagBase {};
struct TagC: public TagBase {};

int main (int argc, const char* argv[])
{
    Namespace nm;
    
    nm.AddTag<TagA>();
    nm.AddTag<TagB>();
    
    assert(nm.HasTag(TagA{}));
    assert(nm.HasTag(TagB{}));
    assert(nm.HasTag(TagC{}) == false);
}

这段代码可以正常运行,但我有以下疑问:

  1. 在此场景下使用**typeid**是否合适?是否具备可移植性?
  2. 因对象切片问题无法在std::list中直接存储TagBase类实例,能否避免使用std::unique_ptr带来的堆分配?
  3. 是否存在更优的设计模式来实现该需求?

我也曾尝试大量使用模板(如std::is_same等),但未找到合适的解决方案。


问题解答

1. typeid的合适性与可移植性

typeid是C标准规定的运算符,只要代码遵循C标准,在任何符合标准的编译器(比如GCC、Clang、MSVC)上都能正常工作,完全具备可移植性。
在这个场景下,TagBase是带有虚析构函数的多态类,typeid(*this)会返回当前对象实际所属派生类的类型信息,用来判断两个TagBase对象是否属于同一派生类完全合理——毕竟需求就是用不同的派生类代表不同标签,用类型匹配判断标签是否存在逻辑自洽。

2. 避免堆分配的方案

完全可以避免堆分配,因为需求本质是跟踪哪些标签类型被添加过,而非存储标签实例的状态。核心思路是直接存储类型标识而非实例:
可以用std::unordered_set<std::type_index>替代std::list<std::unique_ptr<TagBase>>,修改后的代码如下:

#include <typeindex>
#include <unordered_set>

class Namespace {
public:
    template<typename T>
    void AddTag() {
        tags.insert(std::type_index(typeid(T)));
    }
    
    bool HasTag(const TagBase& tag) const {
        return tags.count(std::type_index(typeid(tag))) > 0;
    }

private:
    std::unordered_set<std::type_index> tags;
};

这样既避免了堆分配,还把HasTag的时间复杂度从O(n)降到O(1),效率更高。
如果完全不需要TagBase的多态特性,甚至可以去掉TagBase,直接通过模板参数传递类型:

#include <typeindex>
#include <unordered_set>

class Namespace {
public:
    template<typename T>
    void AddTag() {
        tags.insert(std::type_index(typeid(T)));
    }
    
    template<typename T>
    bool HasTag() const {
        return tags.count(std::type_index(typeid(T))) > 0;
    }
};

// 使用时直接调用nm.HasTag<TagA>(),无需创建临时对象

3. 更优的设计模式

核心需求是运行时维护一个类型集合,标记哪些类型(标签)已被添加,最贴合的方案就是上面提到的std::type_index集合方案,它简洁、高效且符合需求。
如果是编译期的标签需求,可以用模板元编程(比如std::tuple结合类型萃取),但从代码来看,需要的是运行时动态添加标签,所以std::type_index方案最优。
另外,也可以采用**标记类型(Marker Type)**的设计思路,本质和当前的派生类标签一致,但搭配type_index存储类型标识后,完全不需要实例化对象,进一步简化实现。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 16:17:05