类 with `std::variant` 成员如何被安全复制?

How can classes with `std::variant` members be copied safely?

以下示例在删除行 Container container2(container1); 的情况下正确构建和运行。看来 std::variant 的复制构造函数本身已被删除,这使得我的 Container 的复制构造函数被隐式删除。

实际上,我在问:

  1. std::variant 存储为会员的正确方法是什么?
  2. 在允许安全 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;
}

cppreferencestd::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;
};