C++类模板继承场景下派生接口与数据类的协同实现方案
问题背景
参考std::shared_ptr实现简化的引用计数模板类shared_class(暂忽略不严谨的内存逻辑),基础场景下可正常工作,同类型对象共享底层数据,拷贝后修改一个实例的数据会同步到所有共享副本:
template <class C> class shared_class { public: const shared_class& operator=(const shared_class& rhs) { ptr = rhs.ptr; refs[ptr] += 1; return *this; } C* data() { return ptr; } protected: shared_class() { if (ptr == nullptr) ptr = new C; refs[ptr] = 1; } shared_class(const shared_class& other) { ptr = other.ptr; refs[ptr] += 1; } ~shared_class() { if ((refs[ptr] -= 1) == 0) { refs.erase(ptr); delete ptr; } } private: static std::unordered_map<C*, size_t> refs; C* ptr = nullptr; }; template <class C> std::unordered_map<C*, size_t> shared_class<C>::refs = {};
基础使用方式如下,运行后输出符合预期:
class A_data { protected: int foo = 50; friend class A; friend class shared_class<A_data>; }; class A : protected shared_class<A_data> { public: void foo(int f) { data()->foo = f; } int foo() { return data()->foo; } }; using namespace std; int main(){ A a; A b = a; a.foo(40); cout << b.foo(); // 输出40 }
现存问题
需要实现继承自A的子类B,且B对应的底层数据类B_data继承自A_data时,会遇到两难选择:
- 如果直接让B继承A:A继承自
shared_class<A_data>,data()返回A_data*类型,无法访问B_data新增成员,触发编译错误class B_data : public A_data { int bar = 100; friend class B; friend class shared_class<B_data>; }; class B : public A { int bar() { return data()->bar; // 编译错误:A_data不存在成员bar } }; - 如果让B直接继承
shared_class<B_data>:B和A不再有继承关系,无法复用A中已实现的foo接口,必须重复编写相同逻辑,产生冗余代码class B : public shared_class<B_data> { // 必须重复实现A中已有的foo接口,无法复用 void foo(int f) { data()->foo = f; } int foo() { return data()->foo; } int bar() { return data()->bar; } };
优雅实现方案
采用虚函数协变返回+受保护构造函数透传派生数据指针的方案,可以同时满足继承复用、访问派生类成员的需求,且完全兼容原有代码逻辑。
第一步:修正原有代码问题
首先修复原实现中会导致异常的bug:
- 修复
shared_class::operator=的引用计数泄漏问题:原实现未处理自赋值,也没有在替换指针前递减当前持有对象的引用计数 - 为
shared_class添加受保护的带参构造,允许子类传入自定义的派生类数据指针 - 将
data()方法声明为虚函数,支持子类重写时返回协变类型(派生类指针) - 为基数据类
A_data添加虚析构函数,避免通过基类指针析构派生类对象时产生未定义行为 - 修正原代码中的笔误(变量名错误、缺分号、成员变量误写为成员函数等)
修正后的基础代码如下:
#include <unordered_map> #include <iostream> template <class C> class shared_class { public: const shared_class& operator=(const shared_class& rhs) { if (ptr == rhs.ptr) return *this; // 先递减当前指针引用计数,归零时释放资源 if ((refs[ptr] -= 1) == 0) { refs.erase(ptr); delete ptr; } ptr = rhs.ptr; refs[ptr] += 1; return *this; } virtual C* data() { return ptr; } protected: shared_class() : ptr(new C) { refs[ptr] = 1; } // 新增:允许子类传入已构造的派生类指针 explicit shared_class(C* p) : ptr(p) { refs[ptr] = 1; } shared_class(const shared_class& other) : ptr(other.ptr) { refs[ptr] += 1; } virtual ~shared_class() { if ((refs[ptr] -= 1) == 0) { refs.erase(ptr); delete ptr; } } private: static std::unordered_map<C*, size_t> refs; C* ptr = nullptr; }; template <class C> std::unordered_map<C*, size_t> shared_class<C>::refs = {}; // 基数据类添加虚析构 class A_data { public: virtual ~A_data() = default; protected: int foo = 50; friend class A; }; class A : protected shared_class<A_data> { public: void foo(int f) { data()->foo = f; } int foo() { return data()->foo; } protected: // 新增:允许子类传入派生数据类指针 explicit A(A_data* p) : shared_class<A_data>(p) {} A() = default; };
第二步:实现子类B
B公有继承A,构造时向基类传入B_data实例指针,同时利用C++的协变返回特性重写data()方法,返回B_data*类型,即可直接访问B_data的新增成员:
class B_data : public A_data { int bar = 100; friend class B; }; class B : public A { public: B() : A(new B_data) {} // 协变重写data(),返回派生数据类指针,可直接访问新增成员 B_data* data() override { return static_cast<B_data*>(A::data()); } int bar() { return data()->bar; } };
方案优势
- 完全符合继承语义:B是A的直接子类,支持
A*/A&指向B实例的多态场景 - 零代码冗余:A中实现的所有公共接口(如
foo的get/set)B可以直接复用,不需要重复编写 - 类型安全:B中
data()返回B_data*,访问新增成员时编译器会做类型检查,不需要额外的不安全强制转换 - 完全兼容旧代码:原有使用A的逻辑不需要做任何修改,行为和原实现完全一致
验证代码:
using namespace std; int main(){ A a; A b = a; a.foo(40); cout << b.foo() << endl; // 输出40,原有逻辑正常 B b1; b1.foo(20); cout << b1.foo() << endl; // 输出20,复用A的foo接口 cout << b1.bar() << endl; // 输出100,正常访问B_data新增成员 A* a_ptr = &b1; cout << a_ptr->foo() << endl; // 输出20,多态场景正常 return 0; }
内容的提问来源于stack exchange,提问作者daedsidog
相关产品推荐
相关产品推荐

