Ali
Ali

Reputation: 58471

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:

Upvotes: 7

Views: 5976

Answers (3)

Xeo
Xeo

Reputation: 131799

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.

Upvotes: 15

Drew Dormann
Drew Dormann

Reputation: 63775

unique_ptr makes sense in STL containers when the container holds objects that can't be copied. Or if it's costly or simply incorrect to copy them.

You get the same functionality as your

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

But without the memory leaks that v3 is inviting.

Upvotes: 1

Jason
Jason

Reputation: 32510

You actually can iterate though the container without issues using std::unique_ptr<T> ... you just either need to access a reference (i.e., not a copy) of the unique pointer, or you need to actually use an iterator-type with the container. In your case that would be something like vector<unique_ptr<Base>>::iterator or vector<unique_ptr<Base>>::const_iterator.

Upvotes: 1

Related Questions