【问题标题】:When does it make sense to use unique_ptr with STL containers? (C++11)什么时候将 unique_ptr 与 STL 容器一起使用才有意义? (C++11)
【发布时间】:2012-01-06 21:11:52
【问题描述】:

unique_ptr 的容器似乎没有什么意义:您不能将它与初始化列表一起使用,而且我无法遍历容器(以下解决方法)。我是不是误会了什么?或者什么时候使用 unique_ptr 和 STL 容器有意义?

#include <memory>
#include <vector>

using namespace std;

struct Base { void go() { }  virtual ~Base() { } }; 
// virtual ~Base() = default; gives
// "declared virtual cannot be defaulted in the class body" why?

class Derived : public Base { };

int main() {

  //vector<unique_ptr<Base>> v1 = { new Derived, new Derived, new Derived };
  //vector<shared_ptr<Base>> v2 = { new Derived, new Derived, new Derived };
  vector<Base*> v3 = { new Derived, new Derived, new Derived };
  vector<shared_ptr<Base>> v4(v3.begin(), v3.end());
  vector<unique_ptr<Base>> v5(v3.begin(), v3.end());

  for (auto i : v5) { // works with v4
    i->go();
  }
  return 0;
}


以下问题帮助我找到了这些解决方法:

【问题讨论】:

    标签: c++ coding-style stl c++11 unique-ptr


    【解决方案1】:
    for (auto i : v5) {
      i->go();
    }
    

    应该是

    for (auto& i : v5) { // note 'auto&'
      i->go();
    }
    

    否则,您将尝试复制当前元素。

    另外,你不能使用这样的初始化列表,因为std::unique_ptrstd::shared_ptr 的构造函数被标记为explicit。你需要做这样的事情:

    #include <iterator> // make_move_iterator, begin, end
    
    template<class T>
    std::unique_ptr<T> make_unique(){ // naive implementation
      return std::unique_ptr<T>(new T());
    }
    
    std::unique_ptr<Base> v1_init_arr[] = {
        make_unique<Derived>(), make_unique<Derived>(), make_unique<Derived>()
    };
    
    // these two are only for clarity
    auto first = std::make_move_iterator(std::begin(v1_init_arr));
    auto last = std::make_move_iterator(std::end(v1_init_arr));
    std::vector<std::unique_ptr<Base>> v1(first, last);
    
    std::vector<std::shared_ptr<Base>> v2 = {
        std::make_shared<Derived>(),
        std::make_shared<Derived>(),
        std::make_shared<Derived>()
    };
    

    这是一件好事™,否则您可能会泄漏内存(如果后面的构造函数之一抛出,则前面的构造函数尚未绑定到智能指针)。 unique_ptr 的提示是必要的,因为初始化列表复制了它们的参数,并且由于 unique_ptrs 不可复制,你会遇到问题。


    也就是说,我在我的一个项目中使用std::map&lt;std::string, std::unique_ptr&lt;LoaderBase&gt;&gt; 作为加载器字典。

    【讨论】:

    • vector&lt;unique_ptr&lt;Base&gt;&gt; v1 = { make_unique&lt;Derived&gt;(), make_unique&lt;Derived&gt;(), make_unique&lt;Derived&gt;() };
    • 正如@Benjamin 所暗示的,初始化列表根据定义进行复制,因此它们不能与仅移动对象一起使用。
    • 你可以用数组来做到这一点:unique_ptr&lt;Base&gt; v1[] = { make_unique&lt;Derived&gt;(), make_unique&lt;Derived&gt;(), make_unique&lt;Derived&gt;() };
    • @Xeo 示例代码中 v1 的语法是可怕的 :( 我一直在等待 C++Ox,但我还是坚持使用 C++98 并使用旧语法构建,它同样可怕......我错过了重大突破。
    • @Xeo 是的,我得到了安全部分。我期待通过扩展的初始化列表让语法变得更简单,因此我很失望。
    【解决方案2】:

    您实际上可以使用std::unique_ptr&lt;T&gt; 毫无问题地遍历容器...您只需要访问唯一指针的引用(即,不是副本),或者您需要实际使用迭代器类型容器。在您的情况下,这将类似于 vector&lt;unique_ptr&lt;Base&gt;&gt;::iteratorvector&lt;unique_ptr&lt;Base&gt;&gt;::const_iterator

    【讨论】:

      【解决方案3】:

      unique_ptr 在 STL 容器中有意义,因为容器包含无法复制的对象。或者复制它们的成本高或根本不正确。

      您获得与您相同的功能

      vector<Base*> v3 = { new Derived, new Derived, new Derived };
      

      但没有v3 所吸引的内存泄漏。

      【讨论】:

      • 或者当你需要多态行为时。
      • 可以将不可复制的对象(如unique_ptr)放入C++11容器中;虽然有些容器要求它们是可移动的。
      猜你喜欢
      • 2013-10-07
      • 1970-01-01
      • 1970-01-01
      • 2011-10-22
      • 2020-08-03
      • 2011-03-22
      • 1970-01-01
      • 2018-09-30
      • 2015-03-18
      相关资源
      最近更新 更多