如何检测子类是否重写虚方法?CRTP实现相关技术咨询
判断子类是否重写虚方法的优化方案
嘿,你用CRTP实现的思路其实已经挺巧妙了,但确实还有几种更简洁、灵活的方案可以适配不同的场景,我给你逐一梳理:
一、优化现有CRTP实现
你的初始代码可以用C++17引入的std::is_same_v简化写法,再加上constexpr让判断在编译期完成,避免运行期冗余计算,同时建议给子类的重写方法加上override关键字,让编译器帮你校验覆盖的正确性:
#include <type_traits> #include <vector> std::vector<void*> special_list; // 可替换为你需要的基类指针类型 template<class T> class P { public: P() { // 编译期判断子类是否重写了foo if constexpr (!std::is_same_v<decltype(&T::foo), decltype(&P::foo)>) { special_list.push_back(static_cast<T*>(this)); } } virtual ~P() = default; virtual void foo() {} }; class Child : public P<Child> { public: void foo() override {} // 用override明确标记重写 }; class ChildNoOverride : public P<ChildNoOverride> { // 没有重写foo,不会被加入特殊列表 };
二、C++20 用requires表达式更直观
如果你的项目已经升级到C++20,用requires表达式可以让判断逻辑更易读,还能额外校验函数的签名匹配:
#include <type_traits> #include <vector> std::vector<void*> special_list; template<class T> class P { public: P() { if constexpr (requires(T t) { {t.foo()} -> std::same_as<void>; } && !std::is_same_v<decltype(&T::foo), decltype(&P::foo)>) { special_list.push_back(static_cast<T*>(this)); } } virtual ~P() = default; virtual void foo() {} }; class Child : public P<Child> { public: void foo() override {} };
三、脱离CRTP:运行期虚函数表检测
如果你的继承体系不需要CRTP,想要支持普通的多态场景,可以利用虚函数表的特性做运行期检测——重写后的虚函数地址会和基类的不同:
#include <vector> std::vector<class P*> special_list; class P { public: P() { // 获取基类foo的地址和当前实例的foo地址 void (P::*base_foo)() = &P::foo; void (P::*instance_foo)() = static_cast<void (P::*)()>(&std::remove_reference_t<decltype(*this)>::foo); if (base_foo != instance_foo) { special_list.push_back(this); } } virtual ~P() = default; virtual void foo() {} }; class Child : public P { public: void foo() override {} // 会被加入列表 }; class ChildNoOverride : public P { // 不会被加入列表 };
这个方案的好处是不需要模板,适用于所有继承自P的子类,缺点是判断在运行期完成,有极轻微的性能开销,但绝大多数场景下可以忽略。
四、模板特化+标签分发:逻辑更解耦
把“是否重写foo”的判断封装成一个特性模板,再用标签分发执行不同逻辑,代码更清晰且可复用:
#include <type_traits> #include <vector> std::vector<class PBase*> special_list; // 基础特性模板:默认认为没有重写 template<class T, class Base> struct has_overridden_foo : std::false_type {}; // 特化版本:当子类的foo和基类的foo类型不同时,标记为已重写 template<class T, class Base> requires (!std::is_same_v<decltype(&T::foo), decltype(&Base::foo)>) struct has_overridden_foo<T, Base> : std::true_type {}; template<class T> class PBase { public: PBase() { // 用标签分发处理两种情况 add_to_list(std::bool_constant<has_overridden_foo<T, PBase<T>>::value>{}); } virtual ~PBase() = default; virtual void foo() {} private: void add_to_list(std::true_type) { special_list.push_back(static_cast<T*>(this)); } void add_to_list(std::false_type) { // 不做处理 } }; class Child : public PBase<Child> { public: void foo() override {} };
方案选型建议
- 若追求编译期效率且依赖CRTP:优先选优化后的CRTP或模板特化+标签分发;
- 若用C++20且看重代码可读性:选requires表达式方案;
- 若需要支持普通非模板继承体系:选运行期虚函数表检测。
内容的提问来源于stack exchange,提问作者greywolf82
相关产品推荐
相关产品推荐

