7

Example:

struct s { int a; };

s func() { return {42}; }

int main() {
    s new_obj = func(); // line 6
    (void) new_obj;
    return 0;
}

This works. Now, what happens, if we assume that our compiler does no RVO?

  1. func returns a struct of s, so {42} must be converted to s, is then returned and finally copied to new_obj in line 6.
  2. func returns an initializer list, so a deep copy is impossible.

What does the language say? Can you give a proof?

Note: I know that this does not seem useful in this example, but for returning very large, constant sized std::arrays, I do not want to rely on RVO.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Johannes
  • 2,901
  • 5
  • 30
  • 50
  • 4
    “this might be good to know” – except that your compiler **does** in fact perform RVO. – Konrad Rudolph Feb 06 '14 at 13:17
  • @KonradRudolph What if for some reasons, RVO can not be used in one example. And even if - I thought that the C++ language does not guarantee RVO. – Johannes Feb 06 '14 at 13:21
  • @KonradRudolph Shouldn't it move object, instead of applying (or possibly skipping) RVO? – BЈовић Feb 06 '14 at 13:23
  • 1
    @BЈовић: It is moved *semantically*, but in reality RVO could come and optimize it away! Note that RVO is a compiler feature, while move is a language feature... and the language allows the compilers to perform RVO wherever possible. – Nawaz Feb 06 '14 at 13:24
  • 1
    if it is possible it will RVO, if not it will move the object, if it's not possible it will copy it, in this order – Drax Feb 06 '14 at 13:38
  • @Drax No. An implementation is not requiered to do copy elision (RVO). And what do you mean by "it will move the object, if it's not possible ..."? If the move constructor is private or deleted, the program is ill-formed. – MWid Feb 06 '14 at 13:42
  • @BЈовић It should move only if it cannot apply RVO (like Nawaz said, the semantic action is a move but it may be elided). Omitting the move would obviously be better. – Konrad Rudolph Feb 06 '14 at 13:57
  • @Nawaz "and the language allows the compilers to perform RVO wherever possible." No! Every optimization is only allowed under the "as-if rule". RVO in form of what the spec calles "copy elision" is an exception to this rule. – MWid Feb 06 '14 at 13:57
  • @MWid: As if "wherever possible" is anti "as-if rule"! – Nawaz Feb 06 '14 at 14:06
  • @MWid Yes RVO is not required that's why i said "if it is possible". And if the move constructor is not implemented (not explicitly deleted nor private), it will call the copy constructor :) – Drax Feb 06 '14 at 14:31
  • Relevant post: [construction helper make_XYZ allowing RVO and type deduction even if XZY has noncopy constraint](http://stackoverflow.com/questions/19427196/construction-helper-make-xyz-allowing-rvo-and-type-deduction-even-if-xzy-has-non) – Cassio Neri Feb 06 '14 at 15:21

1 Answers1

5

Consider the following example:

#include <iostream>

struct foo {
    foo(int) {}
    foo(const foo&) { std::cout << "copy\n"; }
    foo(foo&&)      { std::cout << "move\n"; }
};

foo f() {
    //return 42;
    return { 42 };
}

int main() {
    foo obj = f();
    (void) obj;
}

When compiled with gcc 4.8.1 with -fno-elide-constructors to prevent RVO the output is

move

If in f the return statement without curly braces is used then, then the output is

move
move

With no RVO, what happens is the following. f must create a temporary object of type foo, let's call it ret, to be returned.

If return { 42 }; is used, then ret is direct initialized from the value 42. So no copy/move constructor was called so far.

If return 42; is used, then another temporary, let's call it tmp is direct initialized from 42 and tmp is moved to create ret. Hence, one move constructor was called so far. (Notice that tmp is an rvalue and foo has a move constructor. If there was no move constructor, then the copy constructor would be called.)

Now ret is an rvalue and is used to initialize obj. Hence the move constuctor is called to move from ret to obj. (Again, in some circumstances, the copy constructor could be called instead.) Hence either one (for return { 42 };) or two (for return 42;) moves happen.

As I said in my comment to the OP's question, this post is very relevant: construction helper make_XYZ allowing RVO and type deduction even if XZY has noncopy constraint. Especially the excelent answer by R. Martinho Fernandes.

Community
  • 1
  • 1
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • 2
    In case of braced-init-lists, `ret` is _copy-list-initialized_! If the converting constructor from `int` to `foo` is declared `explicit`, you get an error. Similarly in the case `return 42;`. It's always _copy-initialization_. – MWid Feb 06 '14 at 16:46
  • @MWid You're right. In addition, if a narrowing conversion is required during *list-initialization* (e.g. `return { 42.0 };`) then the code is ill-formed. I just wanted to focus on the main point of the OP's question (if/when copies/moves are made) and left these other questions aside. – Cassio Neri Feb 06 '14 at 17:02
  • Also see [this self-answered question by Johannes Schaub](http://stackoverflow.com/q/7935639/420683) – dyp Feb 06 '14 at 17:16