Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you move a unique_ptr out of an vector<unique_ptr<Foo>>?

I would like to move a unique_ptr<Foo> out of a vector<unique_ptr<Foo>>. Consider my code:

#include <vector>
#include <memory>
#include <iostream>

using namespace std;

class Foo {
public:
  int x;
  Foo(int x): x(x) {};
  ~Foo() {
    cout << "Destroy of id: " << x << "\n";
    x = -1;
  };
};

int main(int argc, char *argv[]) {
  auto foos = vector<unique_ptr<Foo>>();
  foos.push_back(unique_ptr<Foo>(new Foo(100)));
  foos.push_back(unique_ptr<Foo>(new Foo(101)));
  foos.push_back(unique_ptr<Foo>(new Foo(102)));

  // Print all
  cout << "Vector size: " << foos.size() << "\n";
  for (auto i = foos.begin(); i != foos.end(); ++i) {
    cout << (*i)->x << "\n";
  }

  // Move Foo(100) out of the vector
  {
    auto local = move(foos.at(0));
    cout << "Removed element: " << local->x << "\n";
  }

  // Print all! Fine right?
  cout << "Vector size: " << foos.size() << "\n";
  for (auto i = foos.begin(); i != foos.end(); ++i) {
    cout << (*i)->x << "\n";
  }

  return 0;
}

I expected that it would yield:

Vector size: 3
100
101
102
Removed element: 100
Destroy of id: 100
Vector size: 2
101
102

But instead, I get this result:

Vector size: 3
100
101
102
Removed element: 100
Destroy of id: 100
Vector size: 3
Segmentation fault: 11

Why is my vector size still 3, and why am I getting a segmentation fault? How can I get my desired result?

like image 567
Doug Avatar asked Dec 25 '22 20:12

Doug


1 Answers

Let's simplify your question down to:

vector<unique_ptr<Foo>> foos;
foos.push_back(unique_ptr<Foo>(new Foo(100)));
auto local = std::move(foos[0]);
std::cout << foos[0]->x << '\n';

After you create local by moving foos[0], foos[0] no longer has ownership of a pointer. It is empty. Dereferencing it becomes undefined behavior, which in your case exhibits as a segmentation fault. The vector is perfectly "intact" at this point, it contains one empty unique_ptr, and is equivalently in the state:

vector<unique_ptr<Foo>> foos(1);

You should simply check that your unique_ptr owns a pointer before dereferencing it:

if (foos[0]) {
    // we wouldn't get here
    std::cout << foos[0]->x << '\n';
}

Alternatively, since you want to enforce the invariant that your vector only contains valid pointers, as part of your move-from operation you should just erase that element:

auto local = std::move(foos[0]);
foos.erase(foos.begin());
// now foos is empty
like image 199
Barry Avatar answered Mar 23 '23 00:03

Barry