【问题标题】:Sorting a list of unique_ptrs对 unique_ptrs 列表进行排序
【发布时间】:2018-10-30 13:03:03
【问题描述】:

以下代码将无法编译:

bool ptrLess(unique_ptr<int> ptr1, unique_ptr<int> ptr2)
{
   return *ptr1 < *ptr2;
}

int main()
{
   unique_ptr<int> ptr1(new int(3));
   unique_ptr<int> ptr2(new int(2));
   unique_ptr<int> ptr3(new int(5));
   list<unique_ptr<int>> list;

   list.push_back(ptr1);
   list.push_back(ptr2);
   list.push_back(ptr3);

   list.sort(ptrLess);

   for (auto &element : list) {
      cout << *element;
   }

   return 0;
}

我认为这是因为unique_ptr 的复制构造函数被删除了。我收到如下错误:

错误 C2280: 'std::unique_ptr>::unique_ptr(const std::unique_ptr<_ty>> &)':试图 引用已删除的函数

有没有办法对unique_ptr 的列表进行排序,或许可以使用移动构造函数来代替?

【问题讨论】:

  • 这看起来很像试错法 C++,往往不太顺利。你应该退后一步,从一本好书中系统地学习语言。
  • 你不能复制 unique_ptr's 和 ptrLess 使用按值传递,所以会尝试创建副本。将参数更改为 const refs,它应该可以工作。
  • 顺便说一句,你还有一个类list和一个变量list在同一个范围内,很容易给你带来问题
  • @Zach 好的,那可能是你老师的错。作为一个忠告:链表现在很少有用了,因为它们在几乎所有事情上都非常缓慢。是的,这通常包括在中间插入;当您在列表中找到插入点时,您将很快完成围绕 std::vector 元素的移动。教学人员似乎普遍低估了这一点。
  • 请注意,如果您要为您的ptrLess 使用移动构造函数,那么ptrLess 函数将拥有它正在比较的指针。然后,当函数结束时,将没有所有者,unique_ptr 析构函数将删除所指向的任何内容。您的结果将是一个(技术排序的)空unique_ptrs 列表。

标签: c++ smart-pointers


【解决方案1】:

你应该使用 const ref - 毕竟你不想修改那些指针:

bool ptrLess(const unique_ptr<int>& ptr1, const unique_ptr<int>& ptr2)

如果您的 list 模板是 std::list,那么将参数作为 r 值引用传递将不起作用 - list::sort 必须调用 std::move 有效地重置您的指针。

编辑

至于列出其余代码:std::list 有一个方便的方法,称为 emplace_back(和 emplace_front),它允许您就地构造和附加元素:

your_list.emplace_back(new int(2));

【讨论】:

  • 而且,由于列表的声明方式,它获取传入对象的所有权,对吗? Neato,我喜欢它,但是(至少对我来说)你需要弄清楚那个看起来无辜的单线实际上做了什么。将新构造的对象传递给该方法是unusual(在这种情况下,它作为构造函数的参数传递给unique_ptr,所以没关系,它会在适当的时候被删除)。
【解决方案2】:

试试这个:

#include <memory>
#include <list>
#include <iostream>
using namespace ::std;

bool ptrLess(unique_ptr<int>& ptr1, unique_ptr<int>& ptr2)
{
   return *ptr1 < *ptr2;
}

int main()
{
   unique_ptr<int> ptr1(new int(3));
   unique_ptr<int> ptr2(new int(2));
   unique_ptr<int> ptr3(new int(5));
   list<unique_ptr<int>> list;

   list.push_back(move(ptr1));
   list.push_back(move(ptr2));
   list.push_back(move(ptr3));

   list.sort(ptrLess);

   for (auto &element : list) {
      cout << *element;
   }

   return 0;
}

这里的问题是您需要了解 unique_ptr 的实际目标是:

在处理指针/引用时,如果有多个指针/引用指向同一个对象,就会出现很多潜在的问题。 unique_ptr 试图避免这种情况。 因此,您不能创建 2 个 unique_ptr 引用同一个对象。

你不能使用你的 ptrLess() 函数,因为调用它就像

   unique_ptr<int> ptr1(new int(3));
   unique_ptr<int> ptr2(new int(2));

   ptrLess(ptr1, ptr2);

因为这意味着必须将 ptr1ptr2 复制并传递给 ptrLess() - 这里的关键字是“按值调用”。

而且,你不能这样做

   list<unique_ptr<int>> list;
   unique_ptr<int> ptr1(new int(3));

   unique_ptr<int> ptr1(new int(3));

因为这也是,必须创建ptr1 的副本。 这里的解决方案是不要将 unique_ptr s 传递给 ptrLess 作为值,而是作为参考:

bool ptrLess(unique_ptr<int>& ptr1, unique_ptr<int>& ptr2);

您不会将副本传递到列表中,而是将您的对象移动到那里:

list.push_back(move(ptr1));

这里的关键字是“移动语义”。 这将使您的 ptr1 变量的内容无效 - 该对象已从 ptr1 移出到列表中。

如果你对这些东西更感兴趣,我建议你看看 Rust 语言;)

正如 Baum mit Augen 所指出的,ptrLess 的参数最好声明为 const

bool ptrLess(const unique_ptr<int>& ptr1, const unique_ptr<int>& ptr2);

【讨论】:

  • 至少可以编译,但是比较器不应该使用非常量引用,更重要的是,使用链表对某些值进行排序不是正确的方法。你应该解决并解释这一点。
  • 另外,Rust 与这些有什么关系?
  • 你说得对,我的答案不是最优的——但我不能排除答案中的每一个方面——否则我最终会像 Donald Knuth ;)
  • 合并了您对 const 参数的评论。感谢您的提示!
  • @MichaelBeer:在某处深入细节并跳转到 Rust,最后编译代码。
【解决方案3】:

尝试通过 const ref 传递,这样它就不会复制参数: bool ptrLess(const unique_ptr& ptr1, const unique_ptr& ptr2) { return *ptr1

【讨论】:

    【解决方案4】:

    我突然想到,如果 OP 使用 shared_ptr 而不是 unique_ptr,那么最初发布的代码将保持不变:

    #include <memory>
    #include <list>
    #include <iostream>
    using namespace ::std;
    
    bool ptrLess(const shared_ptr<int>& ptr1, const shared_ptr<int>&  ptr2)
    {
       return *ptr1 < *ptr2;
    }
    
    int main()
    {
       shared_ptr<int> ptr1(new int(3));
       shared_ptr<int> ptr2(new int(2));
       shared_ptr<int> ptr3(new int(5));
       list<const shared_ptr<int>> list;
    
       list.push_back(ptr1);
       list.push_back(ptr2);
       list.push_back(ptr3);
    
       list.sort(ptrLess);
    
       for (auto &element : list) {
          cout << *element;
       }
    
       return 0;
    }
    

    Wandbox 上运行它。

    从某种意义上说,这是一种一贯的做事方式。 push_back 通常会复制要添加到列表中的对象,如果调用者想要使用原始对象,他或她仍然可以使用它。使用shared_ptr 具有相似的语义,而没有复制对象本身的开销。相反,只是复制了shared_ptr,这是一种廉价的操作。

    此外,将 OP 的原始代码修改为 moveunique_ptrs 进入列表本身就很脆弱。它们仍然在调用者的范围内,但不再可用。如果您尝试,您将获得(我假设)nullptr 取消引用。更好的是,这样做(注意额外的大括号):

    ...
    
    list<unique_ptr<int>> list;
    
    {
       unique_ptr<int> ptr1(new int(3));
       unique_ptr<int> ptr2(new int(2));
       unique_ptr<int> ptr3(new int(5));
    
       list.push_back(move(ptr1));
       list.push_back(move(ptr2));
       list.push_back(move(ptr3));
    }
    
    ...
    

    现在你安全了。

    那里的帖子比原始版本好得多,对此感到抱歉。

    【讨论】:

    • 我试图提供 MCVE。这是一个项目,我必须编写一个函数,该函数获取对象的唯一指针列表并对其执行操作。我认为没有必要谈论对象是什么以及我正在执行的操作是什么(它恰好涉及一种排序)。我所问的只是如何对其进行排序。
    • ptrLess 绝对没有理由拥有指针的所有权。让它们共享以便它可以拥有它不需要拥有的指针是一个坏主意。最好用unique_ptr&lt;int&gt; const&amp; 表达缺乏所有权。
    • @DanielH 你所说的将 const ref 传递给ptrLess 是完全正确的,我已经修改了我的答案。但是你错过了重点。 list 需要确保推送到其上的对象在需要访问它们时保持有效,而这正是 shared_ptr 的设计目的。将unique_ptr 用于存储在STL 容器中的任何类型的对象都是自找麻烦。停下来想一想:如果unique_ptr 在容器之前超出范围会发生什么?
    • @PaulSanders 如果您有一个unique_ptrs 的列表,那么指针不会 超出范围,除非您将它们从列表中删除或显式传输它们。 list 拥有指针;这就是他们的重点。如果由于某种原因您需要在容器之间传输对象而不调用它们的移动构造函数,或者您指向的类型不符合容器的所有要求,或者可能是其他情况,这是一件非常合理的事情。这不是通常的事情,因为您通常只是直接存储类型并且间接成本开销,但这并不是不安全的。
    • @Zach 对不起,我不是故意要打败你,我只是想指出你的列表可以只是一个简单的 int 列表。尝试实现它 - 一切都会变得更简单。
    猜你喜欢
    • 2023-04-06
    • 2014-06-21
    • 1970-01-01
    • 2018-07-01
    • 1970-01-01
    • 2018-01-04
    • 2021-12-09
    • 2013-07-09
    相关资源
    最近更新 更多