You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:39:25