基类指针为私有成员时实现多态深拷贝的C++技术问题
实现多态对象的深拷贝(避免派生类类型丢失)
你的问题核心是多态场景下的深拷贝切片问题:当前代码里new A(*a)会触发对象切片,只拷贝基类A的部分,派生类B的成员和类型信息都丢失了,所以c2、c3调用的是A的print方法。
解决这个问题的标准方案是给基类添加虚克隆(clone)函数,让每个派生类实现自己的克隆逻辑,这样就能在运行时根据实际对象类型完成正确的深拷贝,而且扩展性远优于枚举方案。
具体修改步骤:
给基类A添加虚clone方法
把clone声明为虚函数,若A允许实例化,就提供默认实现;如果A是抽象类,就设为纯虚函数。这里我们让A可实例化,所以:virtual A* clone() const { return new A(*this); }派生类B重写clone方法
利用C++的协变返回类型特性,返回B*更直观:B* clone() const override { return new B(*this); }修改类C的构造逻辑
把原来直接new A(*a)的代码,替换为调用对象的clone方法:- 参数化构造函数:
C(A* a) { this->a = a->clone(); } - 拷贝构造函数:
C(const C& copy) { a = nullptr; if (copy.a != nullptr) { a = copy.a->clone(); } }
- 参数化构造函数:
修改后的完整代码:
#include <iostream> class A { protected: double a; double b; public: A() : a(0.0), b(0.0) {} A(double a, double b) : a(a), b(b) {} A(const A& copy) : a(copy.a), b(copy.b) {} // 添加虚clone方法 virtual A* clone() const { return new A(*this); } virtual void print() { std::cout << "print A" << std::endl; std::cout << "a: " << a << ", b: " << b << std::endl; } // 基类要加虚析构函数!避免内存泄漏 virtual ~A() = default; }; class B : public A { private: double c; public: B() : A(), c(0.0) {} B(double a, double b, double c) : A(a, b), c(c) {} B(const B& copy) : A(copy), c(copy.c) {} // 重写clone方法 B* clone() const override { return new B(*this); } virtual void print() { std::cout << "print B" << std::endl; std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl; } }; class C { private: A* a; public: C() : a(nullptr) {} // 修改为调用clone C(A* a) { this->a = a->clone(); } // 拷贝构造也调用clone C(const C& copy) { a = nullptr; if (copy.a != nullptr) { a = copy.a->clone(); } } // 建议添加赋值运算符重载,遵循三/五法则 C& operator=(const C& copy) { if (this != ©) { delete a; a = copy.a ? copy.a->clone() : nullptr; } return *this; } ~C() { delete a; a = nullptr; } void print() { if (a != nullptr) a->print(); } }; int main() { A* a = new A(3.5, 4.5); A* b = new B(3.5, 4.5, 5.5); C c1(a), c2(b), c3(c2); delete a; delete b; c1.print(); c2.print(); c3.print(); }
关键细节说明:
- 虚析构函数:基类A必须添加虚析构函数,否则delete指向派生类的基类指针时,只会调用基类析构函数,导致派生类成员内存泄漏。
- 三/五法则:类C拥有裸指针成员,建议补充赋值运算符重载,确保拷贝赋值时也能正确深拷贝,避免浅拷贝导致的重复释放问题。
- 扩展性:新增派生类时,只需要在新类里重写clone方法即可,完全不用修改基类A和类C的代码,符合开闭原则,比枚举方案灵活得多。
运行修改后的代码,c2和c3会正确调用B的print方法,输出派生类的内容。
内容的提问来源于stack exchange,提问作者Nathan Sandvig
相关产品推荐
相关产品推荐

