I was working on some C++ code using std::move
on shared_ptr
and got really weird output. I've simplified my code as below
int func(std::shared_ptr<int>&& a) {
return 0;
}
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(1);
for (int i = 0; i != 10; ++i) {
func(i == 9 ? std::move(ptr) : std::shared_ptr<int>(ptr));
}
if (ptr) {
std::cout << "ptr is not null: " << *ptr << "\n";
} else {
std::cout << "ptr is null\n";
}
return 0;
}
And I got output
ptr is null
As I expected, my ptr
will be moved (cast to std::shared_ptr<int>&&
) at last loop, and since func
never steals memory in a
, my ptr
outside will be non-null (which turns out to be null actually). And if I replace
func(i == 9 ? std::move(ptr) : std::shared_ptr<int>(ptr));
with if-else statement
if (i == 9) func(std::move(ptr));
else func(std::shared_ptr<int>(ptr));
the output will be
ptr is not null: 1
I am so confused about this behavior of compiler.
I've tried GCC and clang with different std version and optimization level, and got same output. Could someone explain for me why and where the data under ptr
was stolen ?
Because the second and third operand to the conditional operator don't have the same value category (i.e., std::move(ptr)
is an xvalue, while std::shared_ptr<int>(ptr)
is a prvalue), this conditional expression falls under [expr.cond]/7:
Lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:
- The second and third operands have the same type; the result is of that type and the result object is initialized using the selected operand.
- [...]
std::shared_ptr<int>(ptr)
is already a prvalue, so the lvalue-to-rvalue conversion (which is really the glvalue-to-prvalue conversion) does nothing to it.
std::move(ptr)
is converted to a prvalue and that prvalue is used to initialize the result object. The initialization of the result object uses the move constructor (because that's the constructor that initializes a std::shared_ptr<int>
from an xvalue of that type, which is what std::move(ptr)
is). The move constructor "steals" the value from ptr
. The result object is a temporary object that is bound to the parameter a
and then later destroyed. Note that all of this only happens in the case where std::move(ptr)
is actually evaluated (which requires i == 9
to be true).