2014-01-18 10 views
24

私はコピーコンストラクタが呼び出されるC言語で、次のような状況++を知っている:機能として受信した場合、既存のオブジェクトは、それのオブジェクト自身のクラスC++コピーコンストラクタはどのような状況で呼び出されますか?

MyClass A,B; 
A = new MyClass(); 
B=A; //copy constructor called 
  • が割り当てられ

    1. 引数、値渡し、クラスのオブジェクト

      void foo(MyClass a); 
      foo(a); //copy constructor invoked 
      
    2. 関数が(値によって)オブジェクトを返すときクラス

      MyClass foo() 
          { 
           MyClass temp; 
           .... 
           return temp; //copy constructor called 
          } 
      

    の私が作った間違いを訂正すること自由に感じなさい。コピーコンストラクタが呼び出される他の状況がある場合は、より興味があります。

  • +9

    私は 'A = B;'はコピー代入演算子を呼び出したと考えました。 – BWG

    +5

    戻り値最適化(RVO)についても読んでください。最後の例は何もコピーしないかもしれません。 – Mat

    +10

    さらに、 'A = new MyClass();'はコンパイルされません。 –

    答えて

    16

    私はこのことについて間違っているかもしれませんが、このクラスを使用すると呼ばれたときにされたかを確認することができます。

    class a { 
    public: 
        a() { 
         printf("constructor called\n"); 
        }; 
        a(const a& other) { 
         printf("copy constructor called\n"); 
        };  
        a& operator=(const a& other) { 
         printf("copy assignment operator called\n"); 
         return *this; 
        }; 
    }; 
    

    それでは、このコード:

    a b; //constructor 
    a c; //constructor 
    b = c; //copy assignment 
    c = a(b); //copy constructor, then copy assignment 
    

    が、結果としてこれを生成します。

    constructor called 
    constructor called 
    copy assignment operator called 
    copy constructor called 
    copy assignment operator called 
    

    さらに興味深いことに、次のコードがあるとします。

    a* b = new a(); //constructor called 
    a* c; //nothing is called 
    c = b; //still nothing is called 
    c = new a(*b); //copy constructor is called 
    

    これは、ポインターを割り当てるときに実際のオブジェクトに何もしないために発生します。

    +2

    の可能複製;'も忘れてはいけないコピーコンストラクタ – prajmus

    +1

    を呼び出します引数にオブジェクトを渡したり、値でオブジェクトを戻したりすることができます。 –

    +2

    私のコードはすべての可能なイベントを示すものではなく、イベントを見るために使用できるクラスを示しています。 – BWG

    5

    コピーコンストラクタが呼び出される3つの状況があります。 オブジェクトのコピーを作成するとき。 メソッドに値を渡して引数としてオブジェクトを渡すとき。 メソッドから値でオブジェクトを返すとき。

    これらは唯一の状況です....私はコピーコンストラクタが呼び出されたときに...

    3

    次の例だと思います。

    1. 1つのオブジェクトをインスタンス化し、別のオブジェクトの値で初期化するとき。
    2. 値でオブジェクトを渡すとき。
    3. オブジェクトが値によって関数から返されたとき。
    +2

    質問の内容を繰り返しただけです。答えは「いいえ」でなければなりません。 –

    +0

    各ケースのコード例を含めることができますか?それを指摘して – Pandrei

    10

    状況(1)は間違っており、あなたが書いた方法をコンパイルしません。それはでなければなりません:

    MyClass A, B; 
    A = MyClass(); /* Redefinition of `A`; perfectly legal though superfluous: I've 
            dropped the `new` to defeat compiler error.*/ 
    B = A; // Assignment operator called (`B` is already constructed) 
    MyClass C = B; // Copy constructor called. 
    

    ケース(2)で正しいです。

    しかし、(3)の場合、コピーコンストラクタが呼び出されない場合があります。コンパイラが副作用を検出できない場合、戻り値の最適化を実装して不要なディープコピーを最適化します。C++ 11はの値をで形式化しています。

    +0

    おかげで、私は最後の例が真であるとは思わない – Pandrei

    5

    これは基本的に正しいです(#1のあなたのタイプミス以外)。

    さらに注意すべきもう1つのシナリオは、コンテナ内に要素がある場合、要素がさまざまな時点でコピーされることです(たとえば、ベクトルが拡大したり一部の要素が削除された場合など)。これは実際には#1の単なる例ですが、それを忘れるのは簡単です。

    16

    既存のオブジェクトが

    B = A; 
    

    必ずしもそのオブジェクト自身のクラスを割り当てられた場合。この種の代入はコピー代入と呼ばれ、すべてのデータメンバーのメンバーワイズ割り当てを実行するためにクラスの代入演算子が呼び出されることを意味します。実際の関数はMyClass& operator=(MyClass const&)

    ここではは呼び出されません。これは、代入演算子がそのオブジェクトへの参照を取り、したがってコピー構築が実行されないためです。

    コピーの初期化はオブジェクトの初期化時にのみ行われるため、コピーの割り当てはコピーの初期化とは異なります。例えば:

    T y = x; 
        x = y; 
    

    最初の式はxをコピーすることによってyを初期化します。コピーコンストラクタMyClass(MyClass const&)を呼び出します。

    そして前述したように、x = yは代入演算子の呼び出しです。

    (また、copy-elisonと呼ばれるものもあります。これにより、コンパイラがコピーコンストラクタを呼び出すことがなくなります)。


    機能はこれが正しい値によって渡される引数、クラスのオブジェクト

    void foo(MyClass a); 
        foo(a); 
    

    として受信した場合。ただし、C++ 11ではaがxvalueの場合、MyClassに適切なコンストラクタがある場合MyClass(MyClass&&)aはパラメータにmovedとなります。

    (コピーコンストラクタと移動コンストラクタは、クラスのデフォルトのコンパイラ生成メンバー関数のうちの2つです。それらを自分で指定しないと、コンパイラは特定の状況下で寛大な処理を行います)。


    場合関数が返す(値によって)クラスのオブジェクトreturn-value optimizationを通して

    MyClass foo() 
        { 
         MyClass temp; 
         .... 
         return temp; // copy constructor called 
        } 
    

    回答の一部で述べたように、コンパイラは、への呼び出しを削除することができコピーコンストラクタコンパイラオプション-fno-elide-constructorsを使用すると、copy-elisonを無効にして、コピーコンストラクタが実際にこれらの状況で呼び出されることがわかります。

    +0

    私のポストを修正しました。 "return temp"はコピーコンストラクタを呼び出さないが、 "MyClass&ref = temp;"と "return ref;"、今回はコピーコンストラクタが呼び出されます。 – chenlian

    +1

    @chenlian私はこの答えに戻ってきたので、少し正確ではないことがわかりました。 '-fno-Elideの-constructors'が有効でない場合、それは実際に*移動-コンストラクタです*それは、それが利用可能だし、そうでない場合は場合は、コピーコンストラクタが呼び出される最初に呼び出されます。その理由は、 'MyClass&ref = temp; return ref'はコピーコンストラクタを呼び出します。なぜなら、戻り値の最適化はid式を必要とするからです。その場合、明示的な 'std :: move'が必要です。 – 0x499602D2

    2

    他は、説明と参考になっています。

    また、私は大規模な試験内instantations/assigments(C++ 11レディ)の異なるタイプを確認するためにクラスを作成した:

    struct foo : public instantation_profiler<foo> 
    { 
        int value; 
    }; 
    
    
    
    //Me suena bastante que Boost tiene una biblioteca con una parida de este estilo... 
    struct scoped_call 
    { 
    private: 
        std::function<void()> function; 
    
    public: 
        scoped_call(const std::function<void()>& f) : function(f) {} 
    
        ~scoped_call() 
        { 
         function(); 
        } 
    }; 
    
    
    foo f() 
    { 
        scoped_call chapuza([](){ std::cout << "Exiting f()..." << std::endl; }); 
    
        std::cout << "I'm in f(), which returns a foo by value!" << std::endl; 
    
        return foo(); 
    } 
    
    
    void g1(foo) 
    { 
        scoped_call chapuza([](){ std::cout << "Exiting g1()..." << std::endl; }); 
    
        std::cout << "I'm in g1(), which gets a foo by value!" << std::endl; 
    } 
    
    void g2(const foo&) 
    { 
        scoped_call chapuza([](){ std::cout << "Exiting g2()..." << std::endl; }); 
    
        std::cout << "I'm in g2(), which gets a foo by const lvalue reference!" << std::endl; 
    } 
    
    void g3(foo&&) 
    { 
        scoped_call chapuza([](){ std::cout << "Exiting g3()..." << std::endl; }); 
    
        std::cout << "I'm in g3(), which gets an rvalue foo reference!" << std::endl; 
    } 
    
    template<typename T> 
    void h(T&& afoo) 
    { 
        scoped_call chapuza([](){ std::cout << "Exiting h()..." << std::endl; }); 
    
        std::cout << "I'm in h(), which sends a foo to g() through perfect forwarding!" << std::endl; 
    
        g1(std::forward<T>(afoo)); 
    } 
    
    
    int main() 
    { 
        std::cout << std::endl << "Just before a declaration (foo a;)"    << std::endl;          foo a; 
        std::cout << std::endl << "Just before b declaration (foo b;)"    << std::endl;          foo b; 
        std::cout << std::endl << "Just before c declaration (foo c;)"    << std::endl;          foo c; 
        std::cout << std::endl << "Just before d declaration (foo d(f());)"   << std::endl;          foo d(f()); 
    
        std::cout << std::endl << "Just before a to b assigment (b = a)"    << std::endl;          b = a; 
        std::cout << std::endl << "Just before ctor call to b assigment (b = foo())" << std::endl;          b = foo(); 
        std::cout << std::endl << "Just before f() call to b assigment (b = f())"  << std::endl;          b = f(); 
    
    
    
        std::cout << std::endl << "Just before g1(foo) call with lvalue arg (g1(a))"       << std::endl;    g1(a); 
        std::cout << std::endl << "Just before g1(foo) call with rvalue arg (g1(f()))"      << std::endl;    g1(f()); 
        std::cout << std::endl << "Just before g1(foo) call with lvalue ==> rvalue arg (g1(std::move(a)))" << std::endl;    g1(std::move(a)); 
    
        std::cout << std::endl << "Just before g2(const foo&) call with lvalue arg (g2(b))"       << std::endl;  g2(b); 
        std::cout << std::endl << "Just before g2(const foo&) call with rvalue arg (g2(f()))"      << std::endl;  g2(f()); 
        std::cout << std::endl << "Just before g2(const foo&) call with lvalue ==> rvalue arg (g2(std::move(b)))" << std::endl;  g2(std::move(b)); 
    
        //std::cout << std::endl << "Just before g3(foo&&) call with lvalue arg (g3(c))"       << std::endl;   g3(c); 
        std::cout << std::endl << "Just before g3(foo&&) call with rvalue arg (g3(f()))"      << std::endl;   g3(f()); 
        std::cout << std::endl << "Just before g3(foo&&) call with lvalue ==> rvalue arg (g3(std::move(c)))" << std::endl;   g3(std::move(c)); 
    
    
    
        std::cout << std::endl << "Just before h() call with lvalue arg (h(d))"       << std::endl;     h(d); 
        std::cout << std::endl << "Just before h() call with rvalue arg (h(f()))"      << std::endl;     h(f()); 
        std::cout << std::endl << "Just before h() call with lvalue ==> rvalue arg (h(std::move(d)))" << std::endl;     h(std::move(d)); 
    
        foo::print_info(std::cout); 
    } 
    
    :ここ

    #include <iostream> 
    #include <utility> 
    #include <functional> 
    
    
    template<typename T , bool MESSAGES = true> 
    class instantation_profiler 
    { 
    private: 
        static std::size_t _alive , _instanced , _destroyed , 
             _ctor , _copy_ctor , _move_ctor , 
             _copy_assign , _move_assign; 
    
    
    public: 
        instantation_profiler() 
        { 
         _alive++; 
         _instanced++; 
         _ctor++; 
    
         if(MESSAGES) std::cout << ">> construction" << std::endl; 
        } 
    
        instantation_profiler(const instantation_profiler&) 
        { 
         _alive++; 
         _instanced++; 
         _copy_ctor++; 
    
         if(MESSAGES) std::cout << ">> copy construction" << std::endl; 
        } 
    
        instantation_profiler(instantation_profiler&&) 
        { 
         _alive++; 
         _instanced++; 
         _move_ctor++; 
    
         if(MESSAGES) std::cout << ">> move construction" << std::endl; 
        } 
    
        instantation_profiler& operator=(const instantation_profiler&) 
        { 
         _copy_assign++; 
    
         if(MESSAGES) std::cout << ">> copy assigment" << std::endl; 
        } 
    
        instantation_profiler& operator=(instantation_profiler&&) 
        { 
         _move_assign++; 
    
         if(MESSAGES) std::cout << ">> move assigment" << std::endl; 
        } 
    
        ~instantation_profiler() 
        { 
         _alive--; 
         _destroyed++; 
    
         if(MESSAGES) std::cout << ">> destruction" << std::endl; 
        } 
    
    
    
        static std::size_t alive_instances() 
        { 
         return _alive; 
        } 
    
        static std::size_t instantations() 
        { 
         return _instanced; 
        } 
    
        static std::size_t destructions() 
        { 
         return _destroyed; 
        } 
    
        static std::size_t normal_constructions() 
        { 
         return _ctor; 
        } 
    
        static std::size_t move_constructions() 
        { 
         return _move_ctor; 
        } 
    
        static std::size_t copy_constructions() 
        { 
         return _copy_ctor; 
        } 
    
        static std::size_t move_assigments() 
        { 
         return _move_assign; 
        } 
    
        static std::size_t copy_assigments() 
        { 
         return _copy_assign; 
        } 
    
    
        static void print_info(std::ostream& out = std::cout) 
        { 
         out << "# Normal constructor calls: " << normal_constructions() << std::endl 
          << "# Copy constructor calls: " << copy_constructions() << std::endl 
          << "# Move constructor calls: " << move_constructions() << std::endl 
          << "# Copy assigment calls: "  << copy_assigments()  << std::endl 
          << "# Move assigment calls: "  << move_assigments()  << std::endl 
          << "# Destructor calls: "   << destructions()   << std::endl 
          << "# "              << std::endl 
          << "# Total instantations: "  << instantations()  << std::endl 
          << "# Total destructions: "  << destructions()   << std::endl 
          << "# Current alive instances: " << alive_instances()  << std::endl; 
        } 
    }; 
    
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_alive  = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_instanced = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_destroyed = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_ctor  = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_copy_ctor = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_move_ctor = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_copy_assign = 0; 
    template<typename T , bool MESSAGES> 
    std::size_t instantation_profiler<T,MESSAGES>::_move_assign = 0; 
    

    試験であります

    これは-O3GCC 4.8.2-fno-elide-constructorsフラグでコンパイル、テストの抽象的である:

    通常コンストラクタコール:10
    コピーコンストラクタ呼び出し:2
    移動コンストラクタコール:11
    コピーassigmentコール:1
    移動assigment呼び出し:2
    デストラクタコール:19

    合計instantations:23
    総破壊:19の
    現在生きているインスタンス:4

    最後にコピーの省略が有効と同じテスト:

    通常のコンストラクタが呼び出されます:10
    コピーコンストラクタ呼び出し:2
    移動コンストラクタ呼び出し:3
    コピーassigment呼び出し:1
    移動assigmentコール:2
    デストラクタの呼び出し:11

    合計instantations:15の
    総破壊:11の
    現在生きているインスタンス:4

    Hereはideoneで実行される完全なコードです。

    関連する問題