避免消费者函数接收基类时的类型转换:设计方案咨询
合理设计方案:无需类型转换调用子类公共方法(兼顾性能)
方案1:将公共方法抽象到基类(最优解,性能无额外开销)
如果IamSomethingCommunBetweenChilds()是所有BaseConsumable子类的公共行为,最直接的做法是把这个方法声明为基类的纯虚函数(或带默认实现的虚函数):
class BaseConsumable { public: virtual ~BaseConsumable() = default; // 声明公共纯虚方法 virtual void IamSomethingCommunBetweenChilds() = 0; }; class ChildA : public BaseConsumable { public: void IamSomethingCommunBetweenChilds() override { // ChildA的实现逻辑 } }; class ChildB : public BaseConsumable { public: void IamSomethingCommunBetweenChilds() override { // ChildB的实现逻辑 } }; // 消费者类直接调用,无需任何转换 class IOnlyConsumeBaseConsumable { public: void consume(std::shared_ptr<BaseConsumable> consumable) { consumable->IamSomethingCommunBetweenChilds(); // 直接调用虚函数 } };
- 性能:纯虚函数调用仅需一次虚表查找,开销可忽略,是C++多态的标准高效实现。
- 优势:完全符合面向对象设计原则,代码清晰、类型安全,无额外开销。
方案2:引入中间接口类(适配部分子类有该方法的场景)
如果只有部分BaseConsumable子类实现IamSomethingCommunBetweenChilds(),可以定义独立接口类,让需要的子类同时继承BaseConsumable和该接口:
class BaseConsumable { public: virtual ~BaseConsumable() = default; // 基类原有方法 }; // 中间接口:声明公共方法 class IHasCommonMethod { public: virtual ~IHasCommonMethod() = default; virtual void IamSomethingCommunBetweenChilds() = 0; }; // 有公共方法的子类同时继承两个类 class ChildA : public BaseConsumable, public IHasCommonMethod { public: void IamSomethingCommunBetweenChilds() override { // 实现逻辑 } }; // 消费者类可提供重载方法,或兼容原有参数版本 class IOnlyConsumeBaseConsumable { public: // 直接处理带公共方法的子类 void consume(std::shared_ptr<IHasCommonMethod> consumable) { consumable->IamSomethingCommunBetweenChilds(); } // 兼容原有BaseConsumable参数的版本(可选) void consume(std::shared_ptr<BaseConsumable> consumable) { // 安全转换,确定类型时可替换为static_pointer_cast以消除运行时检查开销 if (auto common = std::dynamic_pointer_cast<IHasCommonMethod>(consumable)) { common->IamSomethingCommunBetweenChilds(); } // 无该方法的子类处理逻辑 } };
- 性能:直接使用
IHasCommonMethod参数时,是纯虚函数调用,无额外开销;兼容版本的dynamic_pointer_cast存在一次运行时类型检查,但开销远小于boost::any的类型擦除。 - 优势:保持类型安全,避免强制类型转换风险,适配部分子类的场景。
方案3:CRTP静态多态(极致性能,无虚函数开销)
如果追求零运行时开销,可以用CRTP(奇异递归模板模式)实现静态多态,将方法调用绑定在编译期:
// CRTP基类模板 template<typename Derived> class BaseConsumable { public: void IamSomethingCommunBetweenChilds() { // 编译期绑定到子类的具体实现 static_cast<Derived*>(this)->IamSomethingCommunBetweenChildsImpl(); } }; // 子类继承CRTP基类,实现具体逻辑 class ChildA : public BaseConsumable<ChildA> { public: void IamSomethingCommunBetweenChildsImpl() { // ChildA的实现逻辑 } }; class ChildB : public BaseConsumable<ChildB> { public: void IamSomethingCommunBetweenChildsImpl() { // ChildB的实现逻辑 } }; // 消费者类需模板化,或接收具体子类类型 class IOnlyConsumeBaseConsumable { public: template<typename T> void consume(std::shared_ptr<BaseConsumable<T>> consumable) { consumable->IamSomethingCommunBetweenChilds(); // 编译期绑定,无虚表开销 } };
- 性能:完全消除虚函数调用开销,方法调用在编译期确定,是最快的实现方式。
- 注意:失去动态多态灵活性,消费者需知晓具体子类类型或通过模板适配,代码耦合度稍高,适合性能要求极高的场景。
原有不理想方案的问题说明
- 类型转换:
std::static_pointer_cast是编译期转换,无运行时开销,但需开发者保证类型正确,否则会导致未定义行为;dynamic_pointer_cast是运行时检查,有一定开销,但更安全。两种方式均破坏面向对象封装性,不建议优先使用。 - 基类抛异常的方法:用运行时错误掩盖设计缺陷,代码可读性差、调试难度高,完全不可取。
- boost::any:依赖类型擦除,带来额外运行时开销和代码复杂度,类型安全无法在编译期保证,属于兜底方案,远不如上述设计方案优雅。
内容的提问来源于stack exchange,提问作者Vero
相关产品推荐
相关产品推荐

