C++ 那么在stl集合中可以安全地使用unique_ptr吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2876641/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:21:17  来源:igfitidea点击:

So can unique_ptr be used safely in stl collections?

c++c++11auto-ptrunique-ptr

提问by DanDan

I am confused with unique_ptr and rvalue move philosophy.

我对 unique_ptr 和 rvalue 移动哲学感到困惑。

Let's say we have two collections:

假设我们有两个集合:

std::vector<std::auto_ptr<int>> autoCollection;
std::vector<std::unique_ptr<int>> uniqueCollection;

Now I would expect the following to fail, as there is no telling what the algorithm is doing internally and maybe making internal pivot copies and the like, thus ripping away ownership from the auto_ptr:

现在我希望以下会失败,因为不知道算法在内部做什么,并且可能制作内部枢轴副本等,从而从 auto_ptr 中剥夺所有权:

std::sort(autoCollection.begin(), autoCollection.end());

I get this. And the compiler rightly disallows this happening.

我明白了。编译器正确地禁止这种情况发生。

But then I do this:

但后来我这样做:

std::sort(uniqueCollection.begin(), uniqueCollection.end());

And this compiles. And I do not understand why. I did not think unique_ptrs could be copied. Does this mean a pivot value cannot be taken, so the sort is less efficient? Or is this pivot actually a move, which in fact is as dangerous as the collection of auto_ptrs, and should be disallowed by the compiler?

这编译。我不明白为什么。我不认为 unique_ptrs 可以复制。这是否意味着不能采用枢轴值,因此排序效率较低?或者这个pivot实际上是一个移动,它实际上和auto_ptrs的集合一样危险,应该被编译器禁止?

I think I am missing some crucial piece of information, so I eagerly await someone to supply me with the aha! moment.

我想我错过了一些重要的信息,所以我急切地等待有人向我提供啊哈!片刻。

回答by Matthieu M.

I think it's more a question of philosophy than technic :)

我认为这更像是一个哲学问题而不是技术问题:)

The underlying question is what is the difference between Move and Copy. I won't jump into technical / standardista language, let's do it simply:

潜在的问题是移动和复制之间有什么区别。我不会跳到技术/标准语言,让我们简单地做:

  • Copy: create another identical object (or at least, one which SHOULD compare equal)
  • Move: take an object and put it in another location
  • 复制:创建另一个相同的对象(或至少,一个应该比较相等的对象)
  • 移动:拿一个物体并把它放在另一个位置

As you said, it is possible to implement Move in term of Copy: create a copy into the new location and discard the original. However there are two issues there. One is of performance, the second is about objects used for RAII: which of the two should have ownership ?

正如您所说,可以在复制方面实现移动:在新位置创建一个副本并丢弃原始位置。然而,这里有两个问题。一个是性能,第二个是关于用于 RAII 的对象:两者中的哪一个应该拥有所有权?

A proper Move constructor solves the 2 issues:

一个合适的 Move 构造函数可以解决两个问题:

  • It is clear which object has ownership: the new one, since the original will be discarded
  • It is thus unnecessary to copy the resources pointed to, which allows for greater efficiency
  • 哪个对象拥有所有权很清楚:新的,因为原始的将被丢弃
  • 因此不需要复制指向的资源,这样可以提高效率

The auto_ptrand unique_ptrare a very good illustration of this.

auto_ptrunique_ptr就是其中一个很好的例证。

With an auto_ptryou have a screwed Copy semantic: the original and the copy don't compare equal. You could use it for its Move semantic but there is a risk that you'll lose the object pointed to somewhere.

使用一个auto_ptr你有一个糟糕的复制语义:原始和副本不相等。您可以将其用于 Move 语义,但存在丢失指向某处的对象的风险。

On the other hand, the unique_ptris exactly that: it guarantees a unique owner of the resource, thus avoiding copying and the inevitable deletion issue that would follow. And the no-copy is guaranteed at compile-time too. Therefore, it's suitable in containers as long as you don't try to have copy initialization.

另一方面,这unique_ptr正是:它保证资源的唯一所有者,从而避免复制和随之而来的不可避免的删除问题。并且在编译时也保证无副本。因此,只要您不尝试进行副本初始化,它就适用于容器。

typedef std::unique_ptr<int> unique_t;
typedef std::vector< unique_t > vector_t;

vector_t vec1;                           // fine
vector_t vec2(5, unique_t(new Foo));     // Error (Copy)
vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy)
vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end()));
    // Courtesy of sehe

std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator

std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy)

So you canuse unique_ptrin a container (unlike auto_ptr), but a number of operations will be impossible because they involve copying which the type does not support.

因此,您可以unique_ptr在容器中使用(与 不同auto_ptr),但是许多操作将是不可能的,因为它们涉及类型不支持的复制。

Unfortunately Visual Studio may be quite lax in the enforcement of the standard and also has a number of extensions that you would need to disable to ensure portability of the code... don't use it to check the standard :)

不幸的是,Visual Studio 在执行标准方面可能相当松懈,并且还有许多扩展需要禁用以确保代码的可移植性……不要使用它来检查标准 :)

回答by rlbond

The unique_ptrs are being moved using their move constructor. unique_ptris Movable, but not CopyConstructable.

unique_ptrs的正使用他们的移动构造函数移动。unique_ptr是可移动的,但不是可复制构造的。

There's a great article on rvalue references here. If you haven't read about them yet, or are confused, take a look!

有一个关于右值引用一个伟大的文章在这里。如果您还没有阅读过它们,或者感到困惑,请看一看!

回答by yonil

std::sortcould work only with move operations and no copying, as long as there is only one live copy of each object at any given time. This is a weaker requirement than working in-place, since in principle you could allocate another array temporarily and move all objects to while reordering them.

std::sort只要在任何给定时间每个对象只有一个活动副本,就只能使用移动操作而不能进行复制。这是一个比就地工作更弱的要求,因为原则上您可以临时分配另一个数组并将所有对象移动到重新排序它们。

for example with std::vector<std::unique_ptr<T>>exceeding its capacity, it allocates storage for a larger vector and then moves all objects from old storage to the new one. This is not an in-place operation but it's perfectly valid.

例如,当std::vector<std::unique_ptr<T>>超出其容量时,它会为更大的向量分配存储空间,然后将所有对象从旧存储空间移至新存储空间。这不是就地操作,但它完全有效。

As it turns out, sorting algorithms like quick-sort and heap-sort can in fact work in-place without difficulty. quick-sort's partition routine uses std::swap internally, which counts as a move operation for both objects involved. When selecting a pivot, one trick is to swap it with the first element in the range, this way it will never be moved until partitioning is finished.

事实证明,像快速排序和堆排序这样的排序算法实际上可以毫无困难地就地工作。快速排序的分区例程在内部使用 std::swap,这对于所涉及的两个对象都算作移动操作。选择一个枢轴时,一个技巧是将它与范围中的第一个元素交换,这样在分区完成之前它永远不会被移动。