12
std::unique_ptr<int> ptr() {
    std::unique_ptr<int> p(new int(3));
    return p;  //  Why doesn't this require explicit move using std::move?
}  // Why didn't the data pointed to by 'p' is not destroyed here though p is not moved?

int main() {
    std::unique_ptr<int> a = ptr();  // Why doesn't this require std::move? 
    std::cout << *a; // Prints 3.
}

In the above code, the function ptr() returns a copy of p. When p goes out of scope, the data '3' should get deleted. But how does the code work without any access violation?

M.M
  • 138,810
  • 21
  • 208
  • 365
Alex
  • 245
  • 1
  • 2
  • 6
  • 5
    It actually uses the `std::unique_ptr<>`'s [move constructor](http://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr). – πάντα ῥεῖ Sep 17 '14 at 07:56
  • 4
    Please see [this related question](http://stackoverflow.com/questions/4316727/returning-unique-ptr-from-functions). And whoever closed this as a dupe of "undefined behavior" needs a coffee. – user703016 Sep 17 '14 at 07:56
  • 3
    This is called [copy elision](http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) – M.M Sep 17 '14 at 07:57
  • 2
    @MattMcNabb There still has to be a viable copy/move copy constructor overload. – juanchopanza Sep 17 '14 at 08:02

2 Answers2

16

This is set out in the C++11 standard, § 12.8/32:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue....

(emphasis mine). In plain english, it means that the lvalue p can be treated as an rvalue when it comes to overload resolution, because it is a candidate for copy elision. This in turn means the move constructor is picked up on overload resolution (in actual fact, the move copy is probably elided anyway.)

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
6

Because return of certain expressions, such as local automatic variables, are explicitly defined to return a moved object, if the moving operator is available.

So:

return p;

is more or less similar to:

return std::move(p);

But note that this will not work for example with a global variable.

std::unique_ptr<int> g(new int(3));
std::unique_ptr<int> ptr() {
    return g;  //  error!!!
}
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • This isn't generally true. It only applies in the situation where `p` qualifies for copy elision. – M.M Sep 17 '14 at 08:00
  • @MattMcNabb: Which is on every `return` expression with value. And a few other places. – rodrigo Sep 17 '14 at 08:01
  • @rodrigo No, it isn't that simple. The conditions for copy elision are more complicated than that (unfortunately!) – juanchopanza Sep 17 '14 at 08:03
  • 2
    @rodrigo " in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value" . – M.M Sep 17 '14 at 08:03
  • @MattMcNabb & juanchopanza: Oh! You both are right! A return of a global variable will not qualify for copy elision, for example. – rodrigo Sep 17 '14 at 08:04
  • @rodrigo Other examples: a function parameter, or something inside an if statement. – juanchopanza Sep 17 '14 at 08:06
  • 1
    i.e your answer is still wrong. – juanchopanza Sep 17 '14 at 08:14
  • @juanchopanza: Fair enough. I've rephrased it. In this answer I prefer to avoid excessive technical terms or too much details. – rodrigo Sep 17 '14 at 08:27