如何合法获取不可拷贝(non-copyable)且不可移动(nor-movable)对象的‘副本’?
嘿,先跟你说清楚哈,这真不是那种藏着真实需求的XY问题——我就是单纯对这个点好奇而已,完全没打算用std::memcpy()这种野路子去复制那些本来就设计成不能拷贝的对象哈。
我当然清楚**可拷贝(copyable)**的定义,但有些类虽然把默认的拷贝、移动构造和赋值都禁用了,却留了其他生成“副本”的路子,举个实际的代码例子:
struct Foo { // 明确禁用所有默认拷贝/移动操作 Foo(const Foo&) = delete; Foo& operator=(const Foo&) = delete; Foo(Foo&&) = delete; Foo& operator=(Foo&&) = delete; // 类自身提供的克隆方法,合法生成副本 [[nodiscard]] std::unique_ptr<Foo> clone() const { // 利用私有构造函数创建新实例,复制原对象的数据 return std::unique_ptr<Foo>(new Foo{this->some_data_}); } // 公开构造函数,用于创建新实例 Foo() = default; private: int some_data_ = 42; // 私有构造函数,只允许内部的clone方法调用 Foo(int data) : some_data_(data) {} };
那具体有哪些合法的玩法呢?给你掰扯几个常见的:
类自带克隆接口:这是最规范的方式,类的设计者虽然封死了默认拷贝移动,但专门提供了
clone这类方法,内部用合法的方式复制状态生成新对象,完全符合类的设计意图,不会踩任何坑。序列化+反序列化:如果对象的所有状态都能被读取和重建,那你可以把原对象的成员数据一股脑序列化存起来(比如存成二进制或者键值对),再用这些数据反序列化出一个全新的对象。只要你是通过公开接口获取状态的,这就完全合法。
用公开状态重构新对象:要是对象允许你通过getter方法拿到所有必要的状态数据,那直接用这些数据调用构造函数就行。比如一个不可拷贝的数据库连接配置类,你能通过
get_host()、get_port()拿到所有参数,那直接Config new_cfg(get_host(), get_port())就能得到一个和原对象一模一样的新实例。用智能指针共享实例(不是真副本,但实用):如果你其实不需要完全独立的副本,只是需要多个地方能访问同一个对象的状态,那用
std::shared_ptr来共享所有权就很合适。不过要注意这不是真的副本,原对象改了所有共享的地方都会同步变化,但这确实是合法获取可用实例的方式。
最后必须提醒一句:绝对别用内存复制的野路子(比如memcpy),这会绕过C++的对象生命周期规则,属于未定义行为,程序崩了都是轻的,搞不好出现各种诡异的bug让你查半天都找不到原因。
内容来源于stack exchange

