如何确保C++类层次结构中assign成员函数的正确实现
问题描述
我的类层次结构可简化为如下形式:
class A { int m_A = 1; public: void assign(const A& other) { m_A = other.m_A; } }; class B : public A { int m_B = 2; public: void assign(const B& other) { A::assign(other); m_B = other.m_B; } }; class C : public B { int m_C = 3; public: void assign(const C& other) { B::assign(other); m_C = other.m_C; } }; //... 以此类推
其中assign()成员函数用于复制同类型实例的内容,但该实现存在两大脆弱点:
- 子类未实现
assign() - 子类实现
assign()时忘记调用父类的::assign()
两种情况都会导致程序出现静默错误,例如:
class Berr1 : public A { int m_Berr1 = 2; public: //void assign(const Berr1& other) // 漏写实现! }; class Berr2 : public A { int m_Berr2 = 2; public: void assign(const Berr2& other) { //A::assign(other); // 漏调用父类逻辑! //... } };
我需要一种技术确保继承自A的子类能正确实现assign(),同时满足:
assign()的参数类型必须与当前类一致,若尝试将父类对象(如A)赋值给子类对象(如B),必须触发编译错误;- 不排斥CRTP(奇异递归模板模式),但希望了解其他替代方案。
解决方案
方案一:CRTP静态多态(最可靠)
通过CRTP强制子类实现特定逻辑,同时基类自动处理自身成员的赋值,从根源上避免漏调用父类的问题:
template<typename Derived> class A { int m_A = 1; public: void assign(const Derived& other) { // 基类先处理自身成员赋值 m_A = static_cast<const A&>(other).m_A; // 静态调用子类的专属赋值逻辑(子类必须实现) static_cast<Derived*>(this)->assign_self(other); } }; class B : public A<B> { int m_B = 2; // 子类必须实现此方法,否则编译报错 void assign_self(const B& other) { m_B = other.m_B; } public: // 暴露public的assign接口 using A<B>::assign; }; class C : public A<C> { int m_C = 3; B m_base; void assign_self(const C& other) { m_base.assign(other.m_base); m_C = other.m_C; } public: using A<C>::assign; };
优势:
- 子类不实现
assign_self会直接触发编译错误; - 基类自动处理自身成员,无需子类手动调用父类
assign; assign参数为Derived&,父类对象赋值给子类会触发编译错误,满足需求1。
方案二:C++20概念约束+父类私有成员
利用C++20的concepts强制子类实现assign,结合父类私有成员倒逼子类调用父类逻辑:
#include <concepts> // 定义概念:类必须实现接收自身const引用的assign方法 template<typename T> concept SelfAssignable = requires(T a, const T& b) { { a.assign(b) } -> std::same_as<void>; }; class A { int m_A = 1; // 仅允许子类调用的父类赋值逻辑 void assign_base(const A& other) { m_A = other.m_A; } protected: // 子类必须通过此接口复制父类成员 void copy_parent(const A& other) { assign_base(other); } public: void assign(const A& other) { assign_base(other); } }; // 约束子类必须满足SelfAssignable template<SelfAssignable Derived> class B : public A { int m_B = 2; public: void assign(const B& other) { copy_parent(other); // 必须调用,否则父类成员无法复制 m_B = other.m_B; } };
注意:
- 可强制子类实现
assign,但无法强制调用copy_parent,需配合调试断言补充检查。
方案三:复用默认赋值语义+调试断言
依赖C++默认复制/赋值运算符的自动父类调用特性,结合调试断言检查漏调用情况:
class A { int m_A = 1; bool m_parent_assigned = false; protected: void mark_parent_assigned() { m_parent_assigned = true; } public: A& operator=(const A& other) { if (this != &other) { m_A = other.m_A; mark_parent_assigned(); } return *this; } // 调试模式下检查父类赋值是否被调用 ~A() { #ifndef NDEBUG assert(m_parent_assigned && "父类赋值逻辑未被调用!"); #endif } }; class B : public A { int m_B = 2; public: B& operator=(const B& other) { if (this != &other) { A::operator=(other); // 必须调用,否则调试断言触发 m_B = other.m_B; } return *this; } };
优势:
- 复用C++原生语义,无需额外模板;
- 调试模式下可及时发现漏调用问题;
- 默认赋值运算符参数为自身类型,父类赋值给子类会触发编译错误。
内容的提问来源于stack exchange,提问作者MatG
相关产品推荐
相关产品推荐

