web-dev-qa-db-ja.com

C ++コピーコンストラクターはどのような状況で呼び出されますか?

コピーコンストラクターが呼び出されるc ++の次の状況を知っています。

  1. 既存のオブジェクトに独自のクラスのオブジェクトが割り当てられたとき

    MyClass A,B;
    A = new MyClass();
    B=A; //copy constructor called 
    
  2. 関数が引数として受け取り、値で渡された場合、クラスのオブジェクト

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

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

間違いを訂正してください。しかし、コピーコンストラクターが呼び出される状況が他にある場合は、もっと興味があります。

30
Pandrei

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

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

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

20
BWG

既存のオブジェクトに独自のクラスのオブジェクトが割り当てられている場合

_    B = A;
_

必ずしも。この種の割り当てはcopy-assignmentと呼ばれます。つまり、クラスの割り当て演算子が呼び出され、すべてのデータメンバーのメンバーごとの割り当てが実行されます。実際の関数はMyClass& operator=(MyClass const&)です

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

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

_T y = x;
  x = y;
_

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

前述のように、_x = y_は代入演算子の呼び出しです。

copy-elison と呼ばれるものもあります。これにより、コンパイラはcopy-constructorへの呼び出しを省略します。コンパイラはおそらくこれを使用します)。


関数が値として渡される引数としてクラスのオブジェクトを受け取る場合

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

正解です。ただし、C++ 11では、aがxvalueであり、MyClassに適切なコンストラクターがある場合、MyClass(MyClass&&)amoved パラメータに。

(copy-constructorとmove-constructorは、クラスのコンパイラーが生成するデフォルトの2つのメンバー関数です。ユーザーがそれらを自分で指定しない場合、コンパイラーは特定の状況下で寛大に提供します)。


関数が(値によって)クラスのオブジェクトを返すとき

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

戻り値の最適化 を使用して、いくつかの回答で述べたように、コンパイラーはcopy-constructorの呼び出しを削除できます。コンパイラオプション _-fno-elide-constructors_ を使用することにより、コピーエリソンを無効にして、コピーコンストラクタが確かにこれらの状況で呼び出されます。

24
0x499602D2

状況(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は、これを右辺値参照で形式化します。

12
Bathsheba

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

さらに注意すべき特定のシナリオの1つは、コンテナ内に要素がある場合です。要素はさまざまなタイミングでコピーできます(たとえば、ベクトル内、ベクトルが大きくなったとき、またはいくつかの要素が削除されたとき)。これは実際には1番目の例にすぎませんが、忘れがちです。

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

これらは唯一の状況です...私は思う...

5
Akshay

以下は、コピーコンストラクターが呼び出される場合です。

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

他の人は、説明と参考文献で良い答えを提供しました。

さらに、広範なテストの中で、さまざまなタイプのインスタンス化/アセンブリ(C++ 11対応)をチェックするクラスを作成しました。

#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;

テストは次のとおりです。

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 );
}

これは、GCC 4.8.2および-O3フラグ付きの-fno-elide-constructorsでコンパイルされたテストの要約です。

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

合計インスタンス数:23
全破壊:19
現在のアライブインスタンス:4

最後に、コピー省略を有効にした同じテスト:

通常のコンストラクター呼び出し:10
コピーコンストラクター呼び出し:2
Moveコンストラクター呼び出し:3
コピー呼び出し:1
割り当てコールの移動:2
デストラクタ呼び出し:11

合計インスタンス数:15
全破壊:11
現在のアライブインスタンス:4

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

2
Manu343726