Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does it make sense to use unique_ptr with STL containers? (C++11)

A container of unique_ptr seems to make little sense: you cannot use it with initializer lists and I failed to iterate through the container (workarounds below). Am I misunderstanding something? Or when does it make sense to use unique_ptr and STL containers?

#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;
}


The following questions helped me find these workarounds:
  • How to initialize a container of noncopyable with initializer list?

  • Can I list-initialize a vector of move-only type?

  • when I need containers of NoCopy types I usually use boost::ptr_vector or std::vector<shared_ptr>

like image 266
Ali Avatar asked Jan 06 '12 21:01

Ali


People also ask

Why is unique_ptr useful?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.

Why Auto_ptr Cannot be used with STL?

The C++ Standard says that an STL element must be "copy-constructible" and "assignable." In other words, an element must be able to be assigned or copied and the two elements are logically independent. std::auto_ptr does not fulfill this requirement.

What is a unique_ptr?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.


1 Answers

for (auto i : v5) {
  i->go();
}

Should be

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

Else you'll try to copy the current element.

Also, you can't use an initializer list like that, because the constructors of std::unique_ptr and std::shared_ptr are marked explicit. You need to do something like this:

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

And this is a Good Thing™, because otherwise you might leak memory (if one of the later constructors throws, the former ones aren't yet bound to the smart pointers). The tip-toeing for the unique_ptr is necessary, because initializer lists copy their arguments, and since unique_ptrs aren't copyable, you'd get a problem.


That said, I use a std::map<std::string, std::unique_ptr<LoaderBase>> for a dictionary of loaders in one of my projects.

like image 119
Xeo Avatar answered Sep 27 '22 17:09

Xeo