繁体   English   中英

为什么 std::unique_ptr 不允许自己可复制?

[英]Why doesn't std::unique_ptr allow itself to be copyable?

我知道这是为了防止 unique_ptr 的两个副本有一个可能的悬空指针,其中指向的对象可能已经被释放以及类似的东西。 但是他们为什么不决定允许一种深度复制,而不是仅仅复制指针,他们分配新内存并将复制委托给 unique_ptr 的模板参数类型。

主要问题是,如果它实际上是一个子类,那么只是盲目地用 new 调用模板参数的复制 ctor 将对对象进行切片,这通常使得这样做非常不安全。

也就是说,我发现扩展 unique_ptr 很有用:

template<class T, class D = std::default_delete<T>>
class autoclone_ptr : public std::unique_ptr<T, D> {
 public:
    autoclone_ptr() = default;
    autoclone_ptr(autoclone_ptr &&) = default;
    autoclone_ptr &operator=(autoclone_ptr &&) = default;
    autoclone_ptr(const autoclone_ptr &a) : std::unique_ptr<T,D>(a ? a->clone() : nullptr) {}
    autoclone_ptr &operator=(const autoclone_ptr &a) {
        auto *t = a.get();
        this->reset(t ? t->clone() : nullptr);
        return *this; }
};

这只能为定义了clone()方法的类实例化以避免切片问题。

复制指针时隐式进行深层复制会令人困惑。 通过使其既不能复制构造也不能复制可分配,您被迫做出有意识的决定。

为了调用复制构造函数,您必须使用 make_unique: 为什么 std::make_unique 调用复制构造函数 它会将旧值复制到新内存中。

这个问题很好,其他答案没有提到一个更微妙的原因。

与评论相反,复制与专有所有权兼容。 如果T是可复制的,假设设计可以允许复制unique_ptr<T> 堆分配的决定和允许复制的决定是不同的问题。 我将解决这个问题,他们为什么不使用那个假设的设计?

不完整的类型

C++ 有一个怪癖。 当定义一个类模板Foo<T>当且仅当T可复制时,它应该是可复制的,它不能接受T的不完整类型。 或者,如果接受不完整的类型,则可以撒谎并始终宣传可复制性,而不管T 但是,尝试复制可能是一个硬错误。 例如, std::vector<T>总是宣传它是可复制的,以便它可以接受不完整的类型。

(解释:这是因为复制构造函数不能是模板,也没有返回值,所以 SFINAE 是不可能的。相反,可以为Foo<T>引入基类,这些基类是根据T的属性选择的。这意味着T必须在Foo<T>的实例化时完成。这可能不再适用于 C++20 和延迟约束检查。)

unique_ptr<T>被设计为替代拥有的原始指针T* 毫无疑问, T可能是不完整的。 因此,我们可以使unique_ptr<T>始终不可复制,就像我们所做的那样,或者我们可以撒谎。 如果我们对此撒谎,那么默认情况下,包含unique_ptr<T>的类现在也会撒谎。 在这种情况下, is_copy_constructible<T>将变得不那么有用,因为存在多种类型。

这可以解决吗? 肯定有足够的工作。 我认为 C++20 可能有。 然而,我能想象到的任何解决方案都会对语言进行相当大的改变。 我认为这是它没有完成的最令人信服的原因。

切片

正如一个答案所提到的,如果 (1) 指针指向基类并且 (2) 复制是天真地实现并且 (3) 没有保留比托管指针额外的状态,则切片可能会令人困惑。 有些用途切片不适用,因此人们可以就切片与可复制性的权衡进行真正的辩论。

(3) 不必如此。 首次构造指针时,我们可以隐藏一个函数,该函数会复制最派生的类型。 这将要求从指针到unique_ptr的转换首先发生在最派生的类型上。 这是shared_ptr的删除器采用的方法。 但是我们仍然不能正确地宣传我们的可复制性,所以现在复制变成了运行时错误。 这也使尺寸增加了一倍。

更有趣的是,(2)不一定是这种情况。 clone是完整的样板文件,这意味着实现可以编写它。 但是,复制构造函数已经有意义,因此需要用户可以编写允许此操作的语法。 想象:

virtual T* clone() = default;

但这又回到了第一点。 如果用户需要编写一些东西, unique_ptr<T>的可复制性需要以T的属性为条件,如果我们还想支持不完整的类型,这是不可能的。

未来

有一个针对indirect_value<T>的提案P1950 ,它可能会或可能不会取得进展。 它解决了大多数这些问题。 它限制了复制构造函数,因此它可以正确地宣传可复制性,同时还支持不完整的类型。 它有条件地支持复制。 而且,它还传播 const 就好像它是一个值,而不是一个指针。 这使它成为决定堆分配的理想选择,没有其他包袱。

它不涉及切片,但有一个针对多态类型的兄弟提议。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM