简体   繁体   English

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

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

I know that it is to prevent two copies of unique_ptr to have a possible dangling pointer where the pointed-to object could have been already deallocated and stuff like that.我知道这是为了防止 unique_ptr 的两个副本有一个可能的悬空指针,其中指向的对象可能已经被释放以及类似的东西。 But why didn't they decide to allow a kind of deep copy, where instead of just copying pointers they allocate new memory and delegate the copy to the template argument type of unique_ptr.但是他们为什么不决定允许一种深度复制,而不是仅仅复制指针,他们分配新内存并将复制委托给 unique_ptr 的模板参数类型。

The main problem is that just blindly calling the template argument's copy ctor with new will slice the object if it is actually a subclass, making this a very unsafe thing to do in general.主要问题是,如果它实际上是一个子类,那么只是盲目地用 new 调用模板参数的复制 ctor 将对对象进行切片,这通常使得这样做非常不安全。

That said, I've found it useful to extend unique_ptr:也就是说,我发现扩展 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; }
};

This can only be instantiated for classes that define a clone() method to avoid slicing problems.这只能为定义了clone()方法的类实例化以避免切片问题。

It would be confusing to implicitly make a deep copy when copying a pointer.复制指针时隐式进行深层复制会令人困惑。 By making it neither Copy Constructible nor Copy Assignable you are forced to make a conscious decision.通过使其既不能复制构造也不能复制可分配,您被迫做出有意识的决定。

In order to call the copy constructor you have to use make_unique: Why std::make_unique calls copy constructor .为了调用复制构造函数,您必须使用 make_unique: 为什么 std::make_unique 调用复制构造函数 It will copy the old values to new memory.它会将旧值复制到新内存中。

The question is a good one, and the other answers fail to mention one of the subtler reasons.这个问题很好,其他答案没有提到一个更微妙的原因。

Contrary to a comment, copying is compatible with exclusive ownership.与评论相反,复制与专有所有权兼容。 A hypothetical design could allow copying of unique_ptr<T> if T is copyable.如果T是可复制的,假设设计可以允许复制unique_ptr<T> The decision to heap allocate and the decision to allow copying are distinct concerns.堆分配的决定和允许复制的决定是不同的问题。 I'll approach the question as why did they not use that hypothetical design?我将解决这个问题,他们为什么不使用那个假设的设计?

Incomplete Types不完整的类型

C++ had a quirk. C++ 有一个怪癖。 When defining a class template Foo<T> that should be copyable when and only when T is copyable, it cannot accept incomplete types for T .当定义一个类模板Foo<T>当且仅当T可复制时,它应该是可复制的,它不能接受T的不完整类型。 Alternatively, if one accepts incomplete types, one can lie and always advertise copyability regardless of T .或者,如果接受不完整的类型,则可以撒谎并始终宣传可复制性,而不管T But, attempting to copy may be a hard error.但是,尝试复制可能是一个硬错误。 For example, std::vector<T> always advertises that it is copyable so that it may accept incomplete types.例如, std::vector<T>总是宣传它是可复制的,以便它可以接受不完整的类型。

(Explanation: This is because the copy constructor cannot be a template, and has no return value, so SFINAE is impossible. Instead, one can introduce base classes for Foo<T> which are chosen based on the properties of T . This implies T must be complete at the instantiation of Foo<T> . This may no longer be true with C++20 and deferred constraint checks.) (解释:这是因为复制构造函数不能是模板,也没有返回值,所以 SFINAE 是不可能的。相反,可以为Foo<T>引入基类,这些基类是根据T的属性选择的。这意味着T必须在Foo<T>的实例化时完成。这可能不再适用于 C++20 和延迟约束检查。)

unique_ptr<T> was being designed as a replacement for an owning raw pointer T* . unique_ptr<T>被设计为替代拥有的原始指针T* There are undoubtedly uses where T may be incomplete.毫无疑问, T可能是不完整的。 Therefore, we can either make unique_ptr<T> not copyable always, as was done, or we can lie about it.因此,我们可以使unique_ptr<T>始终不可复制,就像我们所做的那样,或者我们可以撒谎。 If we lie about it, classes that contain unique_ptr<T> now also lie about their copyability by default.如果我们对此撒谎,那么默认情况下,包含unique_ptr<T>的类现在也会撒谎。 In such a situation, is_copy_constructible<T> would become much less useful, because a wide variety of types would lie.在这种情况下, is_copy_constructible<T>将变得不那么有用,因为存在多种类型。

Was this solvable?这可以解决吗? Surely with enough work.肯定有足够的工作。 And I think C++20 may have.我认为 C++20 可能有。 However, any solution I can imagine would be a pretty large change to the language.然而,我能想象到的任何解决方案都会对语言进行相当大的改变。 I think this is the most compelling reason it wasn't done.我认为这是它没有完成的最令人信服的原因。

Slicing切片

As one answer mentioned, slicing could be confusing if (1) the pointer was to a base class and (2) copying were implemented naively and (3) no extra state was kept than the managed pointer.正如一个答案所提到的,如果 (1) 指针指向基类并且 (2) 复制是天真地实现并且 (3) 没有保留比托管指针额外的状态,则切片可能会令人困惑。 There are uses where slicing doesn't apply, so one could have a genuine debate about the trade-offs of slicing versus copyability.有些用途切片不适用,因此人们可以就切片与可复制性的权衡进行真正的辩论。

(3) doesn't have to be the case. (3) 不必如此。 When the pointer is first constructed, we could stash off a function which makes a copy of the most derived type.首次构造指针时,我们可以隐藏一个函数,该函数会复制最派生的类型。 This would require the transition from pointer to unique_ptr happen first at the most derived type.这将要求从指针到unique_ptr的转换首先发生在最派生的类型上。 This was the approach taken for shared_ptr 's deleter.这是shared_ptr的删除器采用的方法。 But we still can't correctly advertise our copyability so copying now becomes a runtime error.但是我们仍然不能正确地宣传我们的可复制性,所以现在复制变成了运行时错误。 This also doubles the size.这也使尺寸增加了一倍。

More interestingly, (2) doesn't have to be the case.更有趣的是,(2)不一定是这种情况。 clone is total boilerplate, which means implementations could write it. clone是完整的样板文件,这意味着实现可以编写它。 However, copy constructors already mean something, so there would need to be a syntax that users could write that would allow this operation.但是,复制构造函数已经有意义,因此需要用户可以编写允许此操作的语法。 Imagine:想象:

virtual T* clone() = default;

But this circles back around to the first point.但这又回到了第一点。 If the user needs to write something, unique_ptr<T> copyability needs to be conditional on properties of T which is impossible if we also want to support incomplete types.如果用户需要编写一些东西, unique_ptr<T>的可复制性需要以T的属性为条件,如果我们还想支持不完整的类型,这是不可能的。

The Future未来

There is a proposal P1950 for indirect_value<T> which may or may not make headway.有一个针对indirect_value<T>的提案P1950 ,它可能会或可能不会取得进展。 It solves most of these issues.它解决了大多数这些问题。 It constrains the copy constructor, so it can correctly advertise copyability while also supporting incomplete types.它限制了复制构造函数,因此它可以正确地宣传可复制性,同时还支持不完整的类型。 It conditionally supports copying.它有条件地支持复制。 And, it also propagates const as if it were a value, instead of a pointer.而且,它还传播 const 就好像它是一个值,而不是一个指针。 This makes it ideal for making the decision to heap allocate, without the other baggage.这使它成为决定堆分配的理想选择,没有其他包袱。

It does not address slicing, but there is a sibling proposal for polymorphic types.它不涉及切片,但有一个针对多态类型的兄弟提议。

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

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