原型模式的核心是虚函数clone(),而非拷贝构造函数;它通过运行时多态实现类型安全的深拷贝,要求基类声明纯虚clone(),子类重写并返回std::unique_ptr,手动处理裸指针等非RAII资源,避免切片、共享句柄和内存泄漏。

原型模式的核心是 clone() 函数,不是拷贝构造函数
很多人一上来就重载拷贝构造函数或 operator=,结果发现对象复制后行为不对——原型模式要求的是“运行时决定复制哪个具体类型”,这必须靠虚函数实现。拷贝构造是编译期绑定的,没法动态分发到子类。
正确做法是:在基类中声明纯虚函数 clone(),每个派生类自己实现它,返回 std::unique_ptr 或 Base*(推荐智能指针):
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<shape> clone() const = 0;
virtual void draw() const = 0;
};
<p>class Circle : public Shape {
double radius<em>;
public:
Circle(double r) : radius</em>(r) {}
std::unique_ptr<shape> clone() const override {
return std::make_unique<circle>(<em>this); // 调用本类拷贝构造
}
void draw() const override { /</em> ... */ }
};</circle></shape></p></shape>
深拷贝陷阱:成员含裸指针或资源句柄时,clone() 必须手动处理
如果类里有 int*、FILE*、std::mutex 等非 RAII 资源,直接调用默认拷贝构造会出问题——两个对象共享同一块内存或句柄,后续释放时崩溃或重复 close。
这时不能依赖 std::make_unique,得显式分配新资源:
- 裸指针成员:在
clone()中 new 新内存,并逐字节拷贝内容 - 文件句柄:不能复制句柄本身,应重新
fopen()并 fseek 到相同偏移 - 互斥量等不可拷贝类型:要么删掉(改用原子变量),要么在
clone()中创建新实例
例如:std::vector 没问题(自动深拷贝),但 int* data_ 就必须手动处理。
为什么返回 std::unique_ptr 而不是 Base*
裸指针需要调用方手动 delete,容易泄漏;而 std::unique_ptr 把所有权语义带进接口,调用方无需关心内存归属,也避免误用 delete 导致二次释放。
注意两点:
- 返回类型必须和基类声明一致,子类不能改成
std::unique_ptr(C++ 不支持协变返回类型用于智能指针) - 若需多态访问,用
auto ptr = shape->clone(); ptr->draw();即可,不需要 downcast - 如果必须返回裸指针(如对接 C API),记得文档写清“调用方负责
delete”
不推荐用 memcpy 或 placement new 做原型复制
有人想绕过构造函数,直接按字节复制对象内存,这在含虚表、引用成员、std::string 等类型时必然失败。错误现象包括:
-
pure virtual method called(虚表指针没更新) - 复制后的
std::string内部指针指向原对象的 buffer,析构时 double-free - 移动后对象的 moved-from 状态被原样复制,导致未定义行为
真正安全的复制路径只有一条:走拷贝构造函数 —— 它由编译器保证调用所有成员的正确拷贝逻辑。所以 clone() 的实现体里,老老实实写 return std::make_unique 就行。
原型模式的复杂点不在语法,而在厘清“谁拥有资源”“哪些成员可位拷贝”“是否要支持移动语义”。这些不提前想清楚,clone() 表面能跑,运行时崩得毫无征兆。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











