Reputation: 1520
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
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
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