Luke Skywalker
Luke Skywalker

Reputation: 1520

rvalue reference and move of a local variable

In this snippet, I allocate a local object B that I pass to the constructor of another object C which takes it as a rvalue reference. I then put the latter into a container.

When I retrieve C and static_cast its member back to B, the value is incorrect (and valgrind identifies 18 errors!)

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

class A {
public:
  virtual ~A() {};
};

class B: public A {
public:
  B(int foo) : _foo(foo) {}
  int _foo;
};

class C {
public:
  C(A&& a): _a(std::move(a)) {}
  A&& _a;
};

std::vector<C> v;

void bar() {
  v.emplace_back(B(12));
}

int main() {
  bar();
  C&& c = std::move(v.front());
  std::cout << static_cast<B&&>(c._a)._foo << std::endl;
  return 0;
}

My understanding is that, as C takes a rvalue reference, there should not be any object slicing. I should still be able to retrieve a full B object. Additionally, my understanding (most probably flawed) of std::move is that I can 'move' a local variable out of its context and that by taking a rvalue reference, C takes ownership of B.

The output is 39931504 instead of 12.

Anyone could explain to me what is going on?

Upvotes: 5

Views: 907

Answers (2)

jfMR
jfMR

Reputation: 24788

The problem is that the reference to A in the class C (i.e., the data member _a) outlives the object it is referring to. This is because the temporary B(12) in your bar() function

void bar() {
   v.emplace_back(B(12));
}

does not exist after returning from bar(). So, the reference to A that the class C contains becomes a dangling reference.


Instead of a temporary, you can create a B object with the new operator:

void bar() {
   B* ptr = new B(12);
   v.emplace_back(std::move(*ptr));
}

This B object doesn't cease to exist when returning from bar(), so the reference to A in C remains valid.

Note that with this approach you need to destroy the object manually:

C&& c = std::move(v.front());
// ...
delete &c._a; // destroy object

Upvotes: 1

bipll
bipll

Reputation: 11950

I should still be able to retrieve a full B object.

You would be if it wasn't a temporary that does not exist by the moment control reaches bar's closing brace. You hold a reference in your C and a reference to a temporary object can quickly become dangling.

A quick fix is to simply change C's member declaration into std::unique_ptr<A> a; and properly initialise it, say, as

template<class AChild> C(AChild child)
  : a(std::make_unique<AChild>(std::move(child))) {};

(add SFINAE by taste).

Upvotes: 2

Related Questions