クラス型変数をreturnした際の流れにつきまして


c++でクラス型変数を返却した際の処理の流れについて質問をさせていただきたいです。

例えば

[code]

    class Hoge{
    //クラス実装
    };
    
    Hoge func(){
      Hoge fuga;
    
      // fugaの作成
    
      return std::move(fuga);
    }
    
    int main(){
      Hoge foo = func();
    
      return 0;
    }
    
[/code]

とした場合、func終了時の処理の流れとしては

  1. fugaを使用したムーブコンスラクトによってfuncの戻り値が構築される
  2. funcの戻り値は右辺値なので、fooはfuncの戻り値によってムーブコンストラクトされる

というもので合っているのでしょうか？  
疑問に思っているのは

  1. fugaは左辺値なので、funcの戻り値をfugaからムーブコンストラクトするには明示的にstd::moveする必要があると思っていますが、これは合っているのでしょうか？
  2. 関数の戻り値によって変数の初期化が行われる場合、ムーブコンストラクトが行われるのでしょうか、それともNRVOなどでコンストラクタが省略されるのでしょうか？  
という部分となります。

よろしくお願いいたします。

【追記】  
使用するC++のバージョンはC++14を想定していますが、  
もしC++17や20で挙動が変わるようでしたら、  
それを含めて教えていただけますと幸いです。




> func終了時の処理の流れとしては
>
>   1. fugaを使用したムーブコンスラクトによってfuncの戻り値が構築される
>   2. funcの戻り値は右辺値なので、fooはfuncの戻り値によってムーブコンストラクトされる
>

>
> というもので合っているのでしょうか？

いずれも部分的には正しいです。厳密な挙動は下記の通りです。

  1. `std::move`関数の明示利用により、常にムーブコンストラクトが行われます。 
    * このケースでは`std::move`関数を利用しない、つまり`return fuga;`という記述が好ましいです。最悪でも暗黙のムーブによるムーブコンストラクトが保証されます。
    * C++コンパイラの最適化によってはNRVO(Named Return Value Optimization)が行われ、ムーブコンストラクタ呼び出しが省略される可能性があります。
  2. C++11〜C++14までと、C++17以降で挙動が異なります。 
    * C++11〜C++14：最悪でもムーブコンストラクトが行われます（コピーコンストラクトではなく）。C++コンパイラの最適化によってはRVO(Return Value Optimization)が行われ、ムーブコンストラクタ呼び出しが省略される可能性があります。
    * C++17以降：常にRVO(Return Value Optimization)が行われると保証されます。

* * *

>
> fugaは左辺値なので、funcの戻り値をfugaからムーブコンストラクトするには明示的にstd::moveする必要があると思っていますが、これは合っているのでしょうか？

いいえ。

ローカル変数の名前（左辺値）をreturn文に指定する場合、まずは右辺値であるかのように読み替えられてムーブコンストラクトが試行されます。この試行がコンパイルエラーとなるようであれば、元の左辺値から改めてコピーコンストラクトが試行されます。

>
> 関数の戻り値によって変数の初期化が行われる場合、ムーブコンストラクトが行われるのでしょうか、それともNRVOなどでコンストラクタが省略されるのでしょうか？

上記回答の通りです。なお、ここでの最適化は"NRVO"ではなく"RVO"です。




提示コードは NRVO ができなくなる典型例ですよね

[code]

    #include <utility>
    class Hoge {};
    Hoge func1() {
        Hoge h;
        return h;
    }
    Hoge func2() {
        Hoge h;
        return std::move(h);
    }
    
[/code]

というコードに対して g++ / clang++ は

[code]

    $ g++ -std=c++14 -W -Wall -pedantic -O -g -c nrvo.cpp 
    nrvo.cpp: In function 'Hoge func2()':
    nrvo.cpp:9:21: warning: moving a local object in a return statement prevents copy elision [-Wpessimizing-move]
        9 |     return std::move(h);
          |            ~~~~~~~~~^~~
    nrvo.cpp:9:21: note: remove 'std::move' call
    $ clang++ -std=c++14 -W -Wall -pedantic -O -g -c nrvo.cpp 
    nrvo.cpp:9:12: warning: moving a local object in a return statement prevents copy elision [-Wpessimizing-move]
        return std::move(h);
               ^
    nrvo.cpp:9:12: note: remove std::move call here
        return std::move(h);
               ^~~~~~~~~~ ~
    1 warning generated.
    $
    
[/code]

のように文句を言いますので、この例では `std::move` を書かないほうが良いと判断されているようです。

[code]

    $ g++ --version
    g++ (GCC) 11.3.0
    Copyright (C) 2021 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    $ clang++ --version
    clang version 8.0.1 (tags/RELEASE_801/final)
    Target: x86_64-unknown-windows-cygnus
    Thread model: posix
    InstalledDir: /usr/bin
    $ uname -a
    CYGWIN_NT-10.0-19044 mypc 3.3.6-341.x86_64 2022-09-05 11:15 UTC x86_64 Cygwin
    $
    
[/code]

ちなみに警告は `-std=c++17` でも表示されました。

RVO/NRVO はほとんどのコンパイラで C++98 の頃から実装されてました（言語規格書的には義務付けられていないけど）ので、下位互換性まで考えて
`std::move` なしで書くべきでしょう。

