问题描述
以下示例将在删除Container container2(container1);
行的情况下构建并正常运行。看来std::variant
本身的副本构造函数已被删除,这使我的Container
的副本构造函数被隐式删除。
有效地,我在问:
- 将
std::variant
存储为成员的正确方法是什么? - 在允许安全复制/移动分配之前,必须在
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
的副本构造函数:
复制构造函数。 [...]除非
std::is_copy_constructible_v<T_i>
中所有T_i
的{{1}}为真,否则将此构造函数定义为已删除。 [...]
换句话说,除非Types
可以包含的一种或多种类型由于某种原因不可复制,否则不会删除它们。就您而言,是造成问题的std::variant
。也许std::unique_ptr
更合适。
扩展Paul Sanders的答案:您想要哪种副本?
如果是浅表副本,请使用shared_ptr<A>
。
如果是深层副本,为什么不拥有variant<..,A>
?如果原因是A
是多态的,那么真正的问题是克隆每个派生类。您必须创建自己的克隆机制和自己的可复制智能指针才能使用它-据我所知,标准库中没有任何东西可以帮助您。
还对现有内容进行了补充:由于否 std::unique_ptr
是可复制的,因此您需要自己实施复制。
在允许安全复制/移动分配之前,必须在
Container
中实现什么?
对于您的特定情况,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&
的{{1}},该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;
};