我对 unique_ptr 和右值移动哲学感到困惑。
假设我们有两个集合:
std::vector<std::auto_ptr<int>> autoCollection;
std::vector<std::unique_ptr<int>> uniqueCollection;
现在我预计以下操作会失败,因为不知道算法在内部做什么,可能会制作内部枢轴副本等,从而剥夺 auto_ptr 的所有权:
std::sort(autoCollection.begin(), autoCollection.end());
我明白了。编译器正确地不允许这种情况发生。
但然后我这样做了:
std::sort(uniqueCollection.begin(), uniqueCollection.end());
这样就可以编译了。我不明白为什么。我不认为 unique_ptrs 可以被复制。这是否意味着无法获取主元值,因此排序效率较低?或者这个枢轴实际上是一个移动,实际上与 auto_ptrs 的集合一样危险,并且应该被编译器禁止?
我认为我错过了一些重要的信息,所以我热切地等待有人向我提供啊哈!片刻。
I am confused with unique_ptr and rvalue move philosophy.
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:
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?
I think I am missing some crucial piece of information, so I eagerly await someone to supply me with the aha! moment.
发布评论
评论(3)
我认为这更多的是一个哲学问题而不是技术问题:)
根本问题是移动和复制之间有什么区别。我不会跳入技术/标准语言,让我们简单地做一下:
正如你所说,它是可以根据复制来实现移动:在新位置创建一个副本并丢弃原始位置。然而,这里有两个问题。一是性能,二是用于 RAII 的对象:两者中哪一个应该拥有所有权?
正确的 Move 构造函数可以解决两个问题:
auto_ptr 和 unique_ptr 很好地说明了这一点。
使用
auto_ptr
你会得到一个扭曲的复制语义:原始和副本不相等。您可以将其用于其移动语义,但存在丢失指向某处的对象的风险。另一方面,
unique_ptr
正是这样的:它保证资源的唯一所有者,从而避免复制和随之而来的不可避免的删除问题。并且在编译时也保证了无复制。因此,只要您不尝试进行复制初始化,它就适用于容器。因此,您可以在容器中使用
unique_ptr
(与auto_ptr
不同),但许多操作是不可能的,因为它们涉及类型所做的复制不支持。不幸的是,Visual Studio 在执行标准方面可能相当宽松,并且还具有许多扩展,您需要禁用这些扩展以确保代码的可移植性...不要使用它来检查标准:)
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:
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 ?
A proper Move constructor solves the 2 issues:
The
auto_ptr
andunique_ptr
are a very good illustration of this.With an
auto_ptr
you 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.On the other hand, the
unique_ptr
is 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.So you can use
unique_ptr
in a container (unlikeauto_ptr
), but a number of operations will be impossible because they involve copying which the type does not support.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 :)
unique_ptr
正在使用其移动构造函数进行移动。unique_ptr
是可移动的,但不是可复制构造的。此处有一篇关于右值引用的精彩文章。如果您还没有阅读过它们,或者感到困惑,请看一下!
The
unique_ptr
s are being moved using their move constructor.unique_ptr
is Movable, but not CopyConstructable.There's a great article on rvalue references here. If you haven't read about them yet, or are confused, take a look!
std::sort
只能用于移动操作,不能进行复制,只要每个对象在任何给定时间只有一个实时副本。这是一个比就地工作更弱的要求,因为原则上您可以临时分配另一个数组并在重新排序时将所有对象移动到其中。例如,当 std::vector> 超出其容量时,它会为更大的向量分配存储空间,然后将所有对象从旧存储移动到新存储。这不是就地操作,但它是完全有效的。
事实证明,像快速排序和堆排序这样的排序算法实际上可以毫无困难地就地工作。快速排序的分区例程在内部使用 std::swap ,这对于所涉及的两个对象都算作移动操作。选择枢轴时,一个技巧是将其与范围中的第一个元素交换,这样在分区完成之前它永远不会被移动。
std::sort
could 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.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.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.