7

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:
Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265

3 Answers3

15
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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    `vector> v1 = { make_unique(), make_unique(), make_unique() };` <-- Did you test that? – Benjamin Lindley Jan 06 '12 at 21:43
  • As @Benjamin alludes to, initializer lists make copies by definition, so they cannot be used with move-only objects. – ildjarn Jan 06 '12 at 21:44
  • @Benjamin: Admittedly, no, since I currently have no compiler available that supports initializer lists like that. I don't see what problem could crop up though? – Xeo Jan 06 '12 at 21:47
  • @Xeo: The problem is that initializer lists are immutable. – Benjamin Lindley Jan 06 '12 at 21:48
  • 1
    You can do it with an array though: `unique_ptr v1[] = { make_unique(), make_unique(), make_unique() };` – Benjamin Lindley Jan 06 '12 at 21:52
  • @Benjamin: Damn, [I actually knew that it won't work](http://stackoverflow.com/q/8468774/500104), but I forgot again! :( Editing my answer right now. – Xeo Jan 06 '12 at 21:52
  • @Benjamin: Yep, and then move the things into the vector. See the edited answer. :) – Xeo Jan 06 '12 at 22:03
  • 1
    @Xeo The syntax for v1 in the example code is hideous :( I was waiting for the C++Ox a lot but I might as well stick with C++98 and use the old syntax for constructing, it is just as hideous... I am missing the big breakthrough. – Ali Jan 06 '12 at 22:13
  • @Benjamin : It will work with `std::array<>` as well, since it's still just aggregate initialization. :-] – ildjarn Jan 06 '12 at 22:53
  • @Ali: The big breakthrough is that you will have no memory leaks in the face of exceptions, since every pointer will be bound to a managing object before the next one is constructed. It doesn't matter if it looks hideous or convoluted, what matters is the safety. – Xeo Jan 07 '12 at 08:18
  • 1
    @Xeo Yes, I get the safety part. I was expecting the syntax getting simpler with the extended initializer lists, hence my disappointment. – Ali Jan 07 '12 at 10:25
1

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.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
1

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.

Jason
  • 31,834
  • 7
  • 59
  • 78