C++オブジェクトモデルのそれらの5:NRV最適化と初期化リスト

6894 ワード

前言
C++オブジェクトモデルの4:コピーコンストラクタでは、1つのオブジェクトを関数パラメータまたは戻り値として使用すると、コピーコンストラクタが呼び出されます.コンパイラはこれらのステップをどのように処理し、どのように最適化しますか.このブログでは、NRVと、初期化リストの踏みやすい「穴」について、コンパイラの最適化操作を紹介します.
NRV
NRVはnamed Return Valueの略で、翻訳すると具名戻り値最適化です.この最適化はコンパイル層で何をしたのか、まず例を見てみましょう.
class Animal{
public: 
    Animal(){
        cout<<"Animal construct!"<0;
    }
    ~Animal(){
        cout<<"Animal destruct!"<const Animal& _animal){
        cout<<"Copy Construct!"<private:
    int age;
};
Animal get(){
    Animal animal;

    return animal;
}
int main(){
    Animal animal = get();
}

ここでは、上記のコードを実行すると何が出力されるかはともかく、読者は考え、疑問を持って下を見続けることができます.
NRVを使用しない最適化
上記の例では、get関数でanimalオブジェクトを定義し、戻り値として返します.コピーコンストラクタを呼び出す方法に従って、コンパイラはコードを次のように拡張します.
  • 最初に追加のパラメータを追加します.タイプはクラスオブジェクトの参照です.このパラメータは、「コピー構築」によって得られる戻り値
  • を配置するために使用されます.
  • は、returnコマンドの前に、上述した新規パラメータの初期値として返信するオブジェクトのコンテンツを使用するためのコピーコンストラクタ呼び出し操作を挿入する.

  • 上記の2つのステップの拡張動作は、以下の代わりに、以下の擬似コードで説明したように聞こえます.
    Animal get(Animal &_result)//                
    {
        Animal animal;
    
        animal.Animal::Animal();//     Anmial      
    
        _result.Animal(animal);//         animal       
    }

    上記の擬似コードの整理に従って,コンパイラの2ステップ拡張挙動を比較的に理解すべきである.面倒そうに見えますが、一時変数animalはまったく役に立ちません.コンパイラはもちろんこのような資源を浪費する対象が存在することを許さないので、今日の主役NRV最適化は入場します.
    NRV最適化
    物事を観察するのはすべて表象から着手して、それから更に底辺を探究します.最初の例の出力結果を見てみましょう.
    Animal construct!
    Animal destruct!

    出力はget関数を呼び出すと、構造関数をコピーすることはなく、最初から最後まで構造関数と構造関数だけが働いていることを示していると思います.これはなぜですか.コピーコンストラクション関数の理解に従って、ここではCopy Constructを出力するはずです!の明らかに、コンパイラは中で手足を動かした.
    animalは完全に資源の浪費に属している以上、コンパイルの存在を遅らせて、直接_resultが代わる?NRVはこのような最適化を行い、以下の場合に最適化される擬似コード:
    Animal get(Animal &_result)//              
    {
        //      animal      ,     _result    
        _result.Animal::Animal();
        //...   animal          _result 
    
        return;//    ,_result        
    }

    ここではちょうど出力に対応しており、構造のみであることがわかります.resultの場合は空の構造関数を呼び出し,関数終了後に構造関数を呼び出した.これにより、一時的なオブジェクトやコピーコンストラクション関数の消費を回避できます.
    追加のテスト
    上記のNRVの説明を経て、筆者は非常に興味を持って、コンパイラがどこでこっそりコードを最適化してくれたのかを見たいと思って、次の2行を書きました.
    Animal a = Animal(1024);
    Animal b = (Animal)1024;

    NRVをしない場合は、まず一時オブジェクトを作成し、コピーコンストラクション関数によって指定されたオブジェクトに一時オブジェクトを割り当てます.コンパイラがそうしないのは明らかで、出力もこれを検証しました.
    Animal construct!
    Animal construct!
    Animal destruct!
    Animal destruct!

    「深さ探索C++オブジェクトモデル」という本では、定義コピーコンストラクタが表示されていない場合、コンパイラはNRV最適化をアクティブにしません.ビッグデータ量のテストでNRV最適化の効果を見ることができると思いますが、ここでは怠けて説明します.
    初期化リスト
    コンストラクション関数を定義する場合、メンバー変数は初期化リストで初期化するか、関数内で初期化します.関数内の初期化は再議論されません.このセクションでは、初期化リストについて説明します.
    次の4つの場合、初期化リストで初期化する必要があります.
  • 参照メンバー変数が最初に開始されたとき
  • 当初constメンバー変数が1つ化されたとき
  • ベースクラスのコンストラクタが呼び出され、パラメータのセットがある場合
  • メンバー遍歴のコンストラクタが呼び出され、パラメータのセットがある場合
  • どうして必要なの?この4つの場合、初期化リストで初期化しないと、プログラムは正しくコンパイルできますが、効率は高くありません.以下のようにします.
    class Animal{
    public:
        string name;
        int age;
        Animal(){
            name = 0;
            age =0;
        }
    };

    上記のテストコードでは、string変数nameの初期化に対して一時stringオブジェクトが生成され、その後、構造関数をコピーしてnameが初期化され、最後に一時オブジェクトが解析されます.疑似コードは次のとおりです.
    Animal::Animal(){
        name.string::string();
    
        //      
        string temp = string(0);
    
        //         
        name.string::operator=(temp);
    
        //      
        temp.string::~string();
    }

    このように効率が非常に低く、初期化リストに入れて初期化すると、次のコードが表示されます.
    Animal::Animal():name(0){
        age = 0;
    }

    コンパイラによる初期化リストの初期化操作は、次の拡張動作をデフォルトで設定します.
    Animal::Animal(){
        name.string::string(0);//            name
        age = 0;
    }

    気をつけて!落とし穴!
    初期化リストのトラップは、面接の問題としてよく使用され、以下のコードを観察します.
    class A{
    public:
        int i;
        int j;
        A(int val):j(val),i(j){}    
    };
    int main(){
        A a(1);
        cout<<"a.i="<cout<<"a.j="<

    初期化リストの理解が不十分であれば,出力1,1を感じることができるが,テスト出力は以下のようになる.
    a.i = 32765
    a.j = 1

    iはランダム値に初期化され,jは1に初期化され,ここではコンパイラがコンストラクタを挿入する順序に関する問題である.
    初期化リストで初期化された変数は、宣言順に構造関数に初期化コードを挿入します.
    まとめ
    このブログでは、まずNRV最適化の概念と実現方法について説明し、テストを行いました.
  • コンパイラは、コピーコンストラクタが定義されていることを示す場合にのみ、NRV最適化
  • をアクティブにする.
  • 関数が複雑になると最適化も実施しにくくなるため、NRVの評価は
  • と異なる.
  • NRVは効率を考慮したものであり、NRVを使用することなくコンパイルエラーを引き起こすことはなく、効率が大幅に低下する
  • である.
    また、初期化リストについては、次の点に注意してください.
  • 変数の初期化順序は、初期化リストにおける順序ではなく、変数の定義によって決定する
  • である.
    このブログはここまでです.
    About Me
    本人も初心者なので、書く過程で間違いがあるのは避けられませんが、読者が気づいたら、以下のメッセージで指摘してください.
    最後に、疑問や議論が必要な場合は、私に連絡してください.連絡先は私の個人ブログaboutページ、住所:AboutMeに会います.
    また、本人の最初のgitbookの本は整理済みで、leetcodeについて問題を解いたので、これをクリックしてOne day One Leetcodeに入ります.
    引き続き注目してください!Thx!