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); }
这段代码可以正常运行,但我有以下疑问:
- 在此场景下使用**
typeid**是否合适?是否具备可移植性? - 因对象切片问题无法在
std::list中直接存储TagBase类实例,能否避免使用std::unique_ptr带来的堆分配? - 是否存在更优的设计模式来实现该需求?
我也曾尝试大量使用模板(如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

