Exact moment of “return” in a C++-function

前端 未结 3 1032
孤城傲影
孤城傲影 2021-02-01 11:48

It seems like a silly question, but is the exact moment at which return xxx; is \"executed\" in a function unambiguously defined?

Please see the following e

3条回答
  •  太阳男子
    2021-02-01 12:36

    There is a concept in C++ called elision.

    Elision takes two seemingly distinct objects and merges their identity and lifetime.

    Prior to c++17 elision could occur:

    1. When you have a non-parameter variable Foo f; in a function that returned Foo and the return statement was a simple return f;.

    2. When you have an anonymous object being used to construct pretty much any other object.

    In c++17 all (almost?) cases of #2 are eliminated by the new prvalue rules; elision no longer occurs, because what used to create a temporary object no longer does so. Instead, the construction of the "temporary" is directly bound to the permanent object location.

    Now, elision isn't always possible given the ABI that a compiler compiles to. Two common cases where it is possible are known as Return Value Optimization and Named Return Value Optimization.

    RVO is the case like this:

    Foo func() {
      return Foo(7);
    }
    Foo foo = func();
    

    where we have a return value Foo(7) which is elided into the value returned, which is then elided into the external variable foo. What appears to be 3 objects (the return value of foo(), the value on the return line, and Foo foo) is actually 1 at runtime.

    Prior to c++17 the copy/move constructors must exist here, and the elision is optional; in c++17 due to the new prvalue rules no copy/move constructr need exist, and there is no option for the compiler, there must be 1 value here.

    The other famous case is named return value optimization, NRVO. This is the (1) elision case above.

    Foo func() {
      Foo local;
      return local;
    }
    Foo foo = func();
    

    again, elision can merge the lifetime and identity of of Foo local, the return value from func and Foo foo outside of func.

    Even c++17, the second merge (between func's return value and Foo foo) is non-optional (and technically the prvalue returned from func is never an object, just an expression, which is then bound to construct Foo foo), but the first remains optional, and requires a move or copy constructor to exist.

    Elision is a rule that can occur even if eliminating those copies, destructions and constructions would have observable side effects; it is not an "as-if" optimization. Instead, it is subtle change away from what a naive person might think C++ code means. Calling it an "optimization" is more than a bit of a misnomer.

    The fact it is optional, and that subtle things can break it, is an issue with it.

    Foo func(bool b) {
      Foo long_lived;
      long_lived.futz();
      if (b)
      {
        Foo short_lived;
        return short_lived;
      }
      return long_lived;
    }
    

    in the above case, while it is legal for a compiler to elide both Foo long_lived and Foo short_lived, implementation issues make it basically impossible, as both objects cannot both have their lifetimes merged with the return value of func; eliding short_lived and long_lived together is not legal, and their lifetimes overlap.

    You can still do it under as-if, but only if you can examine and understand all side effects of destructors, constructors and .futz().

提交回复
热议问题