类 with `std::variant` 成员如何被安全复制?
How can classes with `std::variant` members be copied safely?
以下示例在删除行 Container container2(container1);
的情况下正确构建和运行。看来 std::variant
的复制构造函数本身已被删除,这使得我的 Container
的复制构造函数被隐式删除。
实际上,我在问:
- 将
std::variant
存储为会员的正确方法是什么?
- 在允许安全 copy/move 分配之前必须在
Container
中实现什么?
#include <string>
#include <variant>
class A {};
class Container {
public:
Container(int i) : data_(i) {}
Container(float f) : data_(f) {}
Container(std::string s) : data_(s) {}
Container(std::unique_ptr<A> a) : data_(std::move(a)) {}
std::variant<int, float, std::string, std::unique_ptr<A>> data_;
};
int main() {
Container container1{3};
// error: call to implicitly-deleted copy constructor of 'Container'
//
// copy constructor of 'Container' is implicitly deleted because
// field 'data_' has a deleted copy constructor
Container container2(container1);
return 0;
}
cppreference 对 std::variant
的复制构造函数有这样的说法:
Copy constructor. [...] This constructor is defined as deleted unless std::is_copy_constructible_v<T_i>
is true for all T_i
in Types
. [...]
换句话说,不会删除,除非std::variant
可以包含的一种或多种类型由于任何原因不可复制。在您的情况下,是 std::unique_ptr
导致了问题。或许 std::shared_ptr
更合适。
扩展保罗桑德斯的回答:你想要什么样的文案?
如果是浅拷贝,就用shared_ptr<A>
。
如果是深拷贝,为什么不用variant<..,A>
?如果原因是 A
是多态的,那么真正的问题是克隆每个派生的 类。你必须创建你自己的克隆机制和你自己的可复制智能指针才能使用它——据我所知,标准库中没有任何东西可以帮助你。
你已经得到的东西的另一个补充:你需要自己实现复制,因为 no std::unique_ptr
是可复制的。
What must be implemented in Container
before safe copy/move assignment is allowed?
对于您的特定情况,A
是可复制构造的并且您希望支持复制,它可能如下所示:
Container(const Container& rhs) {
using namespace std; // to make the below less wide
if(holds_alternative<int>(rhs.data_)) data_ = get<int>(rhs.data_);
else if(holds_alternative<float>(rhs.data_)) data_ = get<float>(rhs.data_);
else if(holds_alternative<string>(rhs.data_)) data_ = get<string>(rhs.data_);
// this is the problematic one:
else data_ = make_unique<A>( *get<unique_ptr<A>>(rhs.data_) );
}
std::get
指针并取消引用它 (*
) 并让 A
中的复制构造函数完成它的工作。
这样做没有额外的风险。取消引用 unique_ptr
会得到一个 A&
,您将其提供给 make_unique
,然后将其转发给正在构造的 A
。这是简单的复制,没有任何魔法。
一种稍微复杂的方法可能是创建您自己的 unique_ptr
包装器,允许复制并在您的 variant
中使用它。
示例:
template<typename T, typename D = std::default_delete<T>>
class copyable_obj_ptr {
public:
template<typename... Args>
copyable_obj_ptr(Args&&... args) : ptr(std::forward<Args>(args)...) {}
// moving
copyable_obj_ptr(copyable_obj_ptr&&) = default;
copyable_obj_ptr& operator=(copyable_obj_ptr&&) = default;
// copying
copyable_obj_ptr(const copyable_obj_ptr& rhs) : ptr(new T(*rhs.ptr), D{}) {}
copyable_obj_ptr& operator=(const copyable_obj_ptr& rhs) {
ptr.reset(new T(*rhs.ptr));
return *this;
}
// dereferencing
T& operator*() { return *ptr; }
const T& operator*() const { return *ptr; }
T* operator->() { return ptr.get(); }
const T& operator->() const { return ptr.get(); }
// Add more proxy methods to access the unique_ptr's methods if you need them ...
private:
std::unique_ptr<T, D> ptr;
};
以下示例在删除行 Container container2(container1);
的情况下正确构建和运行。看来 std::variant
的复制构造函数本身已被删除,这使得我的 Container
的复制构造函数被隐式删除。
实际上,我在问:
- 将
std::variant
存储为会员的正确方法是什么? - 在允许安全 copy/move 分配之前必须在
Container
中实现什么?
#include <string>
#include <variant>
class A {};
class Container {
public:
Container(int i) : data_(i) {}
Container(float f) : data_(f) {}
Container(std::string s) : data_(s) {}
Container(std::unique_ptr<A> a) : data_(std::move(a)) {}
std::variant<int, float, std::string, std::unique_ptr<A>> data_;
};
int main() {
Container container1{3};
// error: call to implicitly-deleted copy constructor of 'Container'
//
// copy constructor of 'Container' is implicitly deleted because
// field 'data_' has a deleted copy constructor
Container container2(container1);
return 0;
}
cppreference 对 std::variant
的复制构造函数有这样的说法:
Copy constructor. [...] This constructor is defined as deleted unless
std::is_copy_constructible_v<T_i>
is true for allT_i
inTypes
. [...]
换句话说,不会删除,除非std::variant
可以包含的一种或多种类型由于任何原因不可复制。在您的情况下,是 std::unique_ptr
导致了问题。或许 std::shared_ptr
更合适。
扩展保罗桑德斯的回答:你想要什么样的文案?
如果是浅拷贝,就用shared_ptr<A>
。
如果是深拷贝,为什么不用variant<..,A>
?如果原因是 A
是多态的,那么真正的问题是克隆每个派生的 类。你必须创建你自己的克隆机制和你自己的可复制智能指针才能使用它——据我所知,标准库中没有任何东西可以帮助你。
你已经得到的东西的另一个补充:你需要自己实现复制,因为 no std::unique_ptr
是可复制的。
What must be implemented in
Container
before safe copy/move assignment is allowed?
对于您的特定情况,A
是可复制构造的并且您希望支持复制,它可能如下所示:
Container(const Container& rhs) {
using namespace std; // to make the below less wide
if(holds_alternative<int>(rhs.data_)) data_ = get<int>(rhs.data_);
else if(holds_alternative<float>(rhs.data_)) data_ = get<float>(rhs.data_);
else if(holds_alternative<string>(rhs.data_)) data_ = get<string>(rhs.data_);
// this is the problematic one:
else data_ = make_unique<A>( *get<unique_ptr<A>>(rhs.data_) );
}
std::get
指针并取消引用它 (*
) 并让 A
中的复制构造函数完成它的工作。
这样做没有额外的风险。取消引用 unique_ptr
会得到一个 A&
,您将其提供给 make_unique
,然后将其转发给正在构造的 A
。这是简单的复制,没有任何魔法。
一种稍微复杂的方法可能是创建您自己的 unique_ptr
包装器,允许复制并在您的 variant
中使用它。
示例:
template<typename T, typename D = std::default_delete<T>>
class copyable_obj_ptr {
public:
template<typename... Args>
copyable_obj_ptr(Args&&... args) : ptr(std::forward<Args>(args)...) {}
// moving
copyable_obj_ptr(copyable_obj_ptr&&) = default;
copyable_obj_ptr& operator=(copyable_obj_ptr&&) = default;
// copying
copyable_obj_ptr(const copyable_obj_ptr& rhs) : ptr(new T(*rhs.ptr), D{}) {}
copyable_obj_ptr& operator=(const copyable_obj_ptr& rhs) {
ptr.reset(new T(*rhs.ptr));
return *this;
}
// dereferencing
T& operator*() { return *ptr; }
const T& operator*() const { return *ptr; }
T* operator->() { return ptr.get(); }
const T& operator->() const { return ptr.get(); }
// Add more proxy methods to access the unique_ptr's methods if you need them ...
private:
std::unique_ptr<T, D> ptr;
};