new无法满足克隆需求,因其只能按已知类型构造对象,而克隆需根据现有实例生成同类型独立新实例;原型模式通过虚函数clone()解耦调用方与具体类型,配合工厂注册表实现动态、可扩展的克隆机制。

为什么直接 new 无法满足克隆需求
因为 new 只能按已知类型构造对象,而克隆的本质是“根据一个现有实例,生成同类型但独立的新实例”——你可能连原对象的具体类型名都不知道。比如拿到一个 Shape* 指针,它实际指向 Circle 还是 Rectangle,调用方不关心,但必须能准确复制出同类型的另一个对象。
这时硬写 if (dynamic_cast 不仅冗余,还破坏开闭原则:每加一个子类就得改工厂逻辑。
原型模式的核心就是让每个具体类自己负责“如何复制自己”,抽象工厂则负责统一注册和分发——两者结合,才能把类型信息从调用侧彻底解耦。
原型基类必须实现 clone() 且返回基类指针
关键不是“能复制”,而是“调用方无需知道具体类型”。所以 clone() 必须是虚函数,返回类型得是基类指针(如 Shape*),不能是 auto 或具体子类指针。
常见错误是返回 std::unique_ptr 却在派生类里返回 std::unique_ptr,这会触发 slicing 或编译失败。正确做法是统一用基类智能指针,或裸指针(配合工厂管理生命周期):
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<shape> clone() const = 0; // 统一返回基类智能指针
};</shape>
派生类实现时,用 std::make_unique 构造自身类型,但声明返回类型仍为 std::unique_ptr:
class Circle : public Shape {
double radius_;
public:
Circle(double r) : radius_(r) {}
std::unique_ptr<shape> clone() const override {
return std::make_unique<circle>(*this); // 编译器自动向上转型
}
};</circle></shape>
工厂注册表用 std::map<:string std::function>> 最实用
字符串 ID(如 "circle")比 RTTI 类型名更可控、可读、可配置。注册时传入一个能生成原型的 lambda,而不是原始对象——因为原型对象本身需要被克隆,不能只存一份静态实例(否则多线程下 clone() 可能竞争)。
实操要点:
- 注册用的 lambda 应返回新分配的原型对象,例如
[](){ return std::make_unique(1.0); } - 工厂的
create()方法先查表拿到原型,再调用其clone(),而非直接调用 lambda(否则每次都是新原型,没复用意义) - 如果要用运行时类型名(
typeid(T).name()),注意它不跨平台稳定,GCC 和 MSVC 输出不同,不适合做键
一个轻量注册示例:
class ShapeFactory {
static std::map<:string std::function>()>> registry_;
public:
template<typename t>
static void registerType(const std::string& name) {
registry_[name] = []() -> std::unique_ptr<shape> {
return std::make_unique<t>();
};
}
static std::unique_ptr<shape> create(const std::string& name) {
if (auto it = registry_.find(name); it != registry_.end()) {
return it->second()->clone(); // 先造原型,再克隆
}
return nullptr;
}
};</shape></t></shape></typename></:string>
拷贝构造函数必须真正深拷贝成员,否则 clone 失效
这是最容易被忽略的底层陷阱。很多初学者以为只要写了 clone() 就万事大吉,结果发现克隆出来的对象和原对象共享内部指针——比如 TextShape 里有个 char* 缓存字符串,clone() 直接用了默认拷贝构造,两个对象指向同一块内存。
必须检查每个成员:
- 内置类型、值语义类型(
int、std::string、std::vector)通常没问题 - 裸指针、
std::shared_ptr以外的智能指针、文件句柄、socket 描述符等,必须手动深拷贝或重置 - 若类含虚函数,确保拷贝构造函数不是
= default,除非你确认所有成员都安全
例如:
class TextShape : public Shape {
std::string text_; // 安全,string 自带深拷贝
int* data_; // 危险!需手动处理
public:
TextShape(const std::string& t, int size) : text_(t), data_(new int[size]{}) {}
TextShape(const TextShape& other)
: text_(other.text_), data_(new int[10]) { // 手动分配+拷贝
std::copy(other.data_, other.data_ + 10, data_);
}
std::unique_ptr<shape> clone() const override {
return std::make_unique<textshape>(*this); // 此时才真正深拷贝
}
};</textshape></shape>
原型模式的威力只在深拷贝成立时生效;否则克隆只是换了个地址的别名,后续修改会相互污染。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











