代码之家  ›  专栏  ›  技术社区  ›  DanDan

那么,在STL集合中可以安全地使用惟一的指针吗?

  •  43
  • DanDan  · 技术社区  · 15 年前

    我对独特的价值运动哲学感到困惑。

    假设我们有两个收藏:

    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());
    

    这就是汇编。我不明白为什么。我不认为可以复制唯一的指针。这是否意味着无法获取透视值,因此排序效率较低?或者这个透视图实际上是一个移动,它实际上和自动指针集合一样危险,编译器应该不允许这样做?

    我想我遗漏了一些重要的信息,所以我热切地等待有人给我提供啊哈!时刻。

    3 回复  |  直到 15 年前
        1
  •  52
  •   Matthieu M.    11 年前

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

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

    • 复制:创建另一个相同的对象(或至少一个应该比较相等的对象)
    • 移动:拿一个物体放在另一个地方

    正如您所说,可以根据拷贝来实现移动:将拷贝创建到新位置并丢弃原始位置。但是有两个问题。一个是性能,第二个是关于用于raii的对象:这两个对象中哪一个应该拥有所有权?

    适当的move构造函数解决了两个问题:

    • 很明显哪个对象拥有所有权:新对象,因为原始对象将被丢弃
    • 因此,无需复制所指向的资源,从而提高效率。

    这个 auto_ptr unique_ptr 是一个很好的例子。

    用一个 自动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)
    

    所以你 可以 使用 尤尼奎 在容器中(不像 自动PTR ,但许多操作将不可能,因为它们涉及类型不支持的复制。

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

        2
  •  12
  •   rlbond    15 年前

    这个 unique_ptr 正在使用移动构造函数移动。 尤尼奎 是可移动的,但不是可复制的。

    有一篇关于右值引用的伟大文章 here . 如果你还没有读过它们,或者是很困惑,看看吧!

        3
  •  7
  •   yonil    12 年前

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

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

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

    推荐文章