clone()必须是const成员函数且返回std::unique_ptr<Base>,以确保类型安全克隆;基类声明为virtual std::unique_ptr<Base> clone() const = 0,不加noexcept或final;析构函数须virtual;派生类需正确实现深拷贝并用std::make_unique构造。

clone() 必须是 const 成员函数,且返回 std::unique_ptr
多态对象的 clone() 接口不是可选补充,而是类型安全克隆的强制契约。它必须声明为 const,因为克隆不修改原对象状态;返回类型必须是 std::unique_ptr<Base>(基类指针),不能是裸指针或值类型。
常见错误现象:Base* clone() 导致调用方承担内存管理责任,极易泄漏或 double-free;Base clone() 触发对象切片,丢失派生类虚函数表和成员数据。
-
std::unique_ptr明确转移所有权,析构自动发生,无需手动delete - 协变返回允许派生类写成
std::unique_ptr<Derived> clone() const override,编译器接受且类型更精确 - 必须用
std::make_unique<Derived>(*this)构造,而非new Derived(*this)—— 后者在构造函数抛异常时会内存泄漏
基类声明纯虚 clone() 时,函数签名不能带 noexcept 或 final
基类中若写成 virtual std::unique_ptr<Base> clone() const noexcept = 0;,会导致派生类重写时无法添加异常说明(C++ 要求重写函数的异常规范不能比基类更宽松);加 final 则直接封死继承链,违背多态设计初衷。
正确声明只保留最小契约:
立即学习“C++免费学习笔记(深入)”;
class Base {
public:
virtual ~Base() = default;
virtual std::unique_ptr<Base> clone() const = 0;
};- 不加
noexcept:留给派生类根据自身实现决定是否承诺不抛异常 - 不加
final:保证子类能 override,也允许进一步派生 - 析构函数必须是
virtual,否则通过基类指针 delete 派生对象会未定义行为
派生类实现 clone() 时,拷贝构造必须可访问且深拷贝语义正确
如果派生类成员含原始指针、std::shared_ptr 或自定义资源句柄,仅靠默认拷贝构造会浅拷贝——多个对象共享同一块内存或资源,后续析构时崩溃。
典型场景:一个 Dog 类持有 std::string name 和 int* age_ptr。默认拷贝构造会让两个 Dog 对象指向同一 int*,析构时两次 delete。
- 优先用 RAII 类型(如
std::string、std::vector、std::shared_ptr),它们默认深拷贝 - 若必须用裸指针,需显式定义拷贝构造函数和
operator=,并在其中分配新内存并复制内容 - 验证方式:对克隆后的对象修改其独有成员,确认不影响原对象(例如改
clone()->name = "Buddy",原对象name不变)
客户端调用 clone() 前,要确保原型对象生命周期足够长
常见误用:从 std::unique_ptr<Base> 拿到原始指针后调 get()->clone()。这本身合法,但容易忽略一点:get() 返回的是无所有权指针,若原型指针提前释放,而你又在 clone 过程中访问了已销毁对象的成员(比如虚函数调用或读取成员变量),就会 UB。
- 安全做法:保持原型智能指针存活到 clone 完成,例如
auto proto = std::make_unique<Dog>("Max"); auto copy = proto->clone(); - 若原型是栈对象(如
Dog d("Max"); auto copy = d.clone();),完全没问题——栈对象在作用域内有效,clone()内部只读取其状态 - 避免把
clone()当作“脱离上下文就能任意复制”的黑盒;它的正确性依赖于被克隆对象此时仍有效
原型模式的复杂点不在语法,而在于所有权边界和生命周期的隐式耦合——哪怕 clone() 看起来只是“复制自己”,一旦涉及资源管理或跨线程传递,就很容易漏掉某一处析构时机或共享状态。


















