小問題大思考のC++仮対象

5386 ワード

C++には、コードには見えませんが、確かに存在します.これは、コンパイラによって定義された名前のない非スタックオブジェクト(non-heap object)です.なぜ臨時対象を研究するのか.主にプログラムの性能と効率を高めるために、一時的なオブジェクトの構造と分析はシステムの性能にとって決して微小な影響ではないので、私たちはそれらを理解して、それらがどのようにもたらしたかを知って、できるだけそれらを避けるべきです.
テンポラリオブジェクトは、通常、次の4つのケースで生成されます.
  • タイプ交換
  • 値で
  • を渡す.
  • は、値によって
  • を返す.
  • オブジェクト定義
  • 次に、一つ一つ見てみましょう.
    1、タイプ変換:通常、関数呼び出しを成功させるために一時オブジェクトを生成します.オブジェクトが関数に渡され、そのタイプがバインドされるパラメータタイプとは異なる場合に発生します.
    例:
    void test(const string& str);
    
    char buffer[] = "buffer";
    
    test(buffer); //         

    コンパイラはタイプ変換を行います.bufferをパラメータとしてstring constructorを呼び出すstringタイプの一時オブジェクトを生成します.test関数が返されると、この一時オブジェクトは自動的に破棄されます.
    注:リファレンス(reference)パラメータの場合、変換は、オブジェクトがreference-to-constパラメータに渡された場合にのみ発生します.オブジェクトがreference-to-non-constオブジェクトに渡されると、変換は発生しません.
    例:
    void upper(string& str);
    
    char lower[] = "lower";
    
    upper(lower); //       ,    

    このとき、コンパイラがreference-to-non-constオブジェクトをタイプ変換すると、一時オブジェクトの値が変更されます.これはプログラマーの期待と一致しない.上のコードでは、コンパイラがupperの実行を許可した場合、lowerの値を大文字に変換しますが、これは一時的なオブジェクトにとってchar lower[]の値ですか、それとも「lower」ですか.これはあなたの期待と一致しますか.
    場合によっては、この暗黙的なタイプ変換は私たちが望んでいないので、constructorをexplicitと宣言することで実現することができます.explicitはコンパイラにconstructorをタイプ変換に使用することに反対すると伝えた.
    例:
    explicit string(const char*);

    2、値で渡す:これは通常、関数呼び出しを成功させるために一時的なオブジェクトを生成します.オブジェクトを値で渡すと、実パラメータ対パラメータの初期化は、T formalArg=actualArgと等価になります.
    例:
    void test(T formalArg);
    
    T actualArg;
    test(actualArg);

    コンパイラによって生成される疑似コードは次のとおりです.
    T _temp;
    
    _temp.T::T(acutalArg); //           _temp
    g(_temp);  //      _temp
    _temp.T::~T(); //   _temp

    ローカルパラメータformalArgが存在するため、test()の呼び出しスタックにはformalArgのプレースホルダが存在する.コンパイラは、オブジェクトactualArgの内容をformalArgのプレースホルダにコピーする必要があります.したがって、コンパイラは一時オブジェクトを生成します.
    3、値で返す:関数が値で返される場合、コンパイラは一時的なオブジェクトを生成する可能性が高い.
    例:
    class Integer {
    public:
      friend Integer operator+(const Integer& a, const Integer& b);
      
      Integer(int val=0): value(val) {
      }
      
      Integer(const Integer& rhs): value(rhs.value) {
      }
      
      Integer& operator=(const Integer& rhs);
      
      ~Integer() {
      }
      
    private:
      int value;  
    };
    
    Integer operator+(const Integer& a, const Integer& b) {
      Integer retVal;
      
      retVal.value = a.value + b.value;
      
      return retVal;
    }
    
    Integer c1, c2, c3;
    c3 = c1 + c2;

    コンパイラによって生成される疑似コード:
    struct Integer _tempResult; //      ,       
    operator+(_tempResult, c1, c2); //          
    c3 = _tempResult; // operator=    
    
    Integer operator+(const Integer& _tempResult, const Integer& a, const Integer& b) {
      struct Integer retVal;
      retVal.Integer::Integer(); // Integer(int val=0)  
      
      retVal.value = a.value + b.value;
      
      _tempResult.Integer::Integer(retVal); //       Integer(const Integer& rhs)  ,      。
      
      retVal.Integer::~Integer(); //       
      
      return;
    }
      
      return retVal;
    }

    operator+の戻り値最適化(RVO:Return Value Optimization)を行うと、一時オブジェクトは生成されません.
    例:
    Integer operator+(const Integer& a, const Integer& b) {  
      return Integer(a.value + b.value);
    }

    コンパイラによって生成される疑似コード:
    Integer operator+(const Integer& _tempResult, const Integer& a, const Integer& b) {
      _tempResult.Integer::Integer(); // Integer(int val=0)  
      _tempResult.value = a.value + b.value;
      
      return;
    }

    上記のバージョンと比較すると、一時オブジェクトretValが消去されていることがわかります.
    4、オブジェクト定義:
    例:
    Integer i1(100); //              
    Integer i2 = Integer(100); //            
    Integer i3 = 100; //            

    しかし、実際にはほとんどのコンパイラは最適化によって一時的なオブジェクトを省くので、ここでの初期化形式は基本的に効率的に同じです.
    コメント:
    一時オブジェクトのライフタイム:C++標準によると、一時オブジェクトの破壊は、完全な式を評価する最後のステップです.この完全な式は、一時的なオブジェクトの生成に照らされます.
    完全な式は、通常、一時的なオブジェクト式の最外周を含むものを指します.例:
    ((objA >1024)&&(objB <1024) ) ? (objA - objB) :(objB-objA)
    この式には全部で5つの式が含まれていますが、一番外側の式は?です.任意のサブエクスプレッションで生成された一時オブジェクトは、完全なエクスプレッションが評価された後に破棄される必要があります.
    テンポラリ・オブジェクトのライフサイクル・ルールには2つの例外があります.
    1、式がobjectを初期化するために使用される場合.例:
    String progName("test");
    String progVersion("ver-1.0");
    String progNameVersion = progName + progVersion

    ProgName+progVersionで生成された一時オブジェクトが式の評価が終了した後にプロファイル化される場合、progName Versionは生成されません.したがって,C++標準では,式の実行結果を含む一時オブジェクトはobjectの初期化操作が完了するまで保持すべきである.
    注意:
    const char* progNameVersion = progName + progVersion

    この初期化操作は必ず失敗します.コンパイラによって生成される疑似コードは次のとおりです.
    String _temp;
    operator+(_temp, progName, progVersion);
    progNameVersion = _temp.String::operator char*();
    _temp.String::~String();

    2、一時オブジェクトがreferenceにバインドされている場合.例:
    const String& name = "C++";

    コンパイラによって生成される疑似コードは次のとおりです.
    String _temp;
    temp.String::String("C++");
    const String& name = _temp;

    この場合、C++は、標準的には、一時オブジェクトがreferenceにバインドされている場合、初期化されたreferenceの生命が終了するまでオブジェクトが保持されるか、一時オブジェクトの生命範囲が終了するまで--どの状況が先に到着するかによって決まる.
    参考書:
    1、『深度探索:C++対象モデル』
    2、『C++性能向上プログラミング技術』
    3、《Effective C++》
    4、《more effective C++》
    5、『C++言語の設計と進化』