如何规避C++多态包装器派生类复制时的间接层?
实现值语义多态包装类的间接层优化问题
我希望实现一个具有值语义的多态类型包装类,对标提案P0201和P3019的设计:对于基类Base及其派生类Derived,包装器Poly<Base>可以容纳Derived对象,且复制Poly<Base>时会创建Derived的深拷贝。
核心控制块基础实现
核心思路是采用可克隆的控制块,简化后的代码如下:
struct BaseControl<BaseType> { virtual auto clone() -> BaseControl<BaseType>* = 0; virtual auto object() -> BaseType* = 0; }; struct Control<BaseType, DerivedType> : public BaseControl<BaseType> { auto clone() -> BaseControl<BaseType>* {return new Control(*this);} auto object() -> BaseType* {return std::addressof(object);} DerivedType object; }; struct Poly<BaseType> { Poly(InPlace<DerivedType>) : control{new Control<BaseType, DerivedType>()} {} Poly(Poly const& other) : control{other.control->clone()} {} BaseControl<BaseType>* control; };
为简洁起见,代码经过简化,忽略缺失的模板关键字、特殊成员函数、内存泄漏等问题
P0201的派生类构造方案及痛点
P0201允许从Poly<Derived>构造Poly<Base>,参考实现通过委托控制块完成:
struct DelegatingControl<BaseType, DerivedType> : public BaseControl<BaseType> { auto clone() -> BaseControl<BaseType>* {return new DelegatingControl(subControl->clone());} auto object() -> BaseType* {return subControl->object();} BaseControl<DerivedType>* subControl; };
但这种实现会导致每次从派生类包装器复制/移动时,引入额外的间接层,我想知道是否有办法规避这个问题。
优化尝试:移除BaseControl中的BaseType
我的思路是剥离BaseControl中的BaseType参数,重新设计控制块:
struct BaseControl { virtual auto clone() -> BaseControl* = 0; virtual auto object_address() -> void* = 0; }; struct Control<DerivedType> : public BaseControl { auto clone() -> BaseControl* {return new Control<DerivedType>(*this);} auto object_address() -> void* {return std::addressof(object);} DerivedType object; };
但克隆后如何正确访问对象成为难题,需要计算从BaseControl到目标类型的偏移量,对应的Poly实现如下:
struct Poly<BaseType> { Poly(InPlace<DerivedType>) : control{new Control<DerivedType>()}, ptr{static_cast<DerivedType*>(control->object_address())} {} BaseControl* control; BaseType* ptr; }; // 从Poly<Derived>移动构造的实现很简单 Poly<BaseType>(Poly<DerivedType> other) { control = std::exchange(other.control, nullptr); ptr = std::exchange(other.ptr, nullptr); } // 复制构造的实现存在疑问 Poly<BaseType>(Poly<BaseType> const& other) { control = other.control->clone(); auto offset = (void*) other.ptr - other.control->object_address(); // 是否允许? ptr = (BaseType*)(control->object_address() + offset); // 是否允许? }
除了void*算术运算本身不被标准允许(可转换为unsigned char*规避),我不确定这段代码是否会引入未定义/未指定行为。
其他备选方案的局限
- 若将
BaseType*替换为成员指针BaseType BaseControl::*,标准不允许将其转换为Derived BaseControl::*,会破坏从Poly<Derived>的构造逻辑。 - 要求所有类型派生自某个虚基类,这种方案不够优雅,侵入性太强。
内容的提问来源于stack exchange,提问作者Dominik
相关产品推荐
相关产品推荐

