In term of move semantics on return value, I heard that the best practice is to return the object as is, and don't call std::move to it, as it prevent return value optimizations. However, consider the following code.
class MyTestClass {
public:
// intentionally implicit constructor
MyTestClass(const std::string& s) {
std::cerr << "construct " << s << std::endl;
}
MyTestClass(std::string&& s) {
std::cerr << "move construct " << s << std::endl;
}
MyTestClass(int s) {
std::cerr << "construct int " << s << std::endl;
}
};
void testRVO() {
auto lambda = [](int t) -> MyTestClass {
if (t == 0) {
std::string s("hello");
return s; // use move, RVO
} else if (t == 1) {
std::optional<std::string> s("hello");
// or unique pointer
// std::unique_ptr<std::string> s = std::make_unique<std::string>("hello");
return *s; // use copy, no RVO !!!
} else if (t == 2) {
std::optional<std::string> s("hello");
return std::move(*s); // use move
} else {
return 1;
}
};
lambda(0);
lambda(1);
lambda(2);
lambda(-1);
}
MyTestClass simulates a generic wrapper/container (think of std::variant) that can be implicitly constructed from const T& and T&&.
In the first case, I returned a string as is, and move constructor is called, which is what you would expect.
In the second case, I constructed an std::optional (or std::unique_ptr) and returns its value using the dereference operator. However, in this case, copy constructor is called instead.
In the third case, similar to previous case, I explicitly called std::move in the return statement and the move constructor is called.
I am not sure why the copy constructor is called in the second case, as the dereference operator returns a lvalue reference, which is an lvalue. The string s in first case is also an lvalue. So I would expect them to be treated the same. Is the compiler just not smart enough to tell that the std::optional would be destroyed after return anyway? Or is it because I am using implicit construction?
In practice, I would obviously want to use the third case, since copying s can be much more expensive than moving it (consider s is a larger, more complex object), but it still feels wrong to write return std::move(...);
Could someone explain why this happens? When should I use std::move in return statement?
return s; // use move, RVO- the comment is confusing.sis notMyTestClassand is copied and not moved.return *s; // use copy, no RVO !!!is also confusing. RVO is applied toMyTestClass.std::optionalis not available is C++11, why do you tag the question so?std::moveto it" - Inreturn *s;you aren't returning "the object as is", you are returning an expression*s.return'ed.