2010-05-20 69 views
42

我与unique_ptr和右值移动哲学混淆。那么unique_ptr可以在stl集合中安全使用吗?

比方说,我们有两个类别:

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的集合一样危险,应该被编译器禁止?

我想我错过了一些关键信息,所以我热切地等待有人给我提供aha!时刻。

+1

其实编译器*应该*抱怨'std :: vector > autoCollection;'因为COAPS(自动指针的容器)不允许在任何描述中。 – 2010-05-20 18:55:45

+0

使用VS2010。我甚至没有在/ W4发出警告。 – DanDan 2010-05-20 19:42:04

+1

或收到auto_ptr折旧的警告。 – DanDan 2010-05-20 20:01:34

回答

50

我认为它更比哲学TECHNIC的问题:)

根本的问题是,什么是移动和复制的区别。我不会跳进技术/ standardista语言,让我们这样做只是:

  • 复制:创建另一个相同的对象(或至少一个应该比较相等)
  • 移动:取一个对象,并把它在另一个位置

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

一个适当的转移构造解决了2个问题:

  • 很清楚哪一个对象具有所有权:新的一个,因为原来的会被丢弃
  • 因此,不需要复制的资源指出, ,这允许更高的效率

auto_ptrunique_ptr是一个很好的例子。

随着auto_ptr你有一个拧的复制语义:原件和副本不比较相等。您可以将其用于移动语义,但存在您将丢失指向某处的对象的风险。

另一方面,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) 

所以,你可以使用在容器unique_ptr(不像auto_ptr),而是因为它们涉及该类型不支持复制多项业务将是不可能的。

不幸的是,Visual Studio在标准的实施中可能相当松懈,并且还有一些扩展需要禁用以确保代码的可移植性...不要用它来检查标准:)

+0

谢谢你,很好的回答和例子:) – DanDan 2010-05-21 19:29:20

+3

在你的评论“罚款,因为使用移动构造函数”你的意思是“移动任务或交换”?另外,为了完整起见,下面是'std :: move'和'std :: make_move_iterator'完成'不可能'的方法:http://coliru.stacked-crooked.com/a/6b032d70a9ed6f5c – sehe 2013-09-19 13:18:12

+0

@sehe:你是对,我的意思是移动分配操作员。 – 2013-09-19 13:39:00

11

unique_ptr正在被移动使用他们的移动构造函数。 unique_ptr是可移动的,但不是CopyConstructable。

有一篇关于右值引用here的文章。如果你还没有读过它们,或者感到困惑,请看看!

+0

嘿,感谢这篇伟大的文章!虽然在第7页有点疯狂,但是我学到了很多东西 但是我原来的问题仍然存在。为什么移动安全但拷贝不是?不是unique_ptr的移动与auto_ptr的副本相同?如果对象的移动构造函数使用std :: move,即所有权的转移,是不是auto_ptr副本的默认行为? – DanDan 2010-05-20 20:20:06

+1

是的,但auto_ptr来自C++ 0x之前,并且明确表示不是允许在容器中使用 另一方面,由于unique_ptr可以安全地移动,因此您(和编译器)可以放心,不会违反所有权。另外,请记住,STL的排序算法是未指定的,并且在C++ 0x中只需要移动东西即可。但是可以在原地进行快速排序,因此不需要任何副本。 – rlbond 2010-05-20 22:10:10

7

std::sort只能在移动操作和不复制的情况下工作,只要每个对象在任何给定时间只有一个活动副本即可。由于原则上你可以暂时分配另一个数组,并将所有对象移动到重新排序的位置,这是一个比在原地更弱的要求。例如

例如std::vector<std::unique_ptr<T>>超过其容量,它为较大的向量分配存储空间,然后将所有对象从旧存储设备移动到新存储设备。这不是一个就地操作,但它是完全有效的。

事实证明,快速排序和堆排序等排序算法实际上可以毫无困难地就地工作。快速排序的分区例程在内部使用std :: swap,这将作为所涉及对象的移动操作。当选择一个数据透视表时,一个技巧就是将它与该范围内的第一个元素进行交换,这样在分区完成之前,它将永远不会移动。

相关问题