Подтвердить что ты не робот

В каких ситуациях называется конструктор копии С++?

Я знаю следующие ситуации в С++, где будет вызываться конструктор копирования:

  • когда существующему объекту присваивается объект собственного класса

    MyClass A,B;
    A = new MyClass();
    B=A; //copy constructor called 
    
  • если функции получают в качестве аргумента, переданного по значению, объект класса

    void foo(MyClass a);
    foo(a); //copy constructor invoked
    
  • когда функция возвращает (по значению) объект класса

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

Пожалуйста, не стесняйтесь исправить любые ошибки, которые я сделал; но мне более любопытно, есть ли другие ситуации, в которых вызывается конструктор копирования.

4b9b3361

Ответ 1

Возможно, я ошибаюсь, но этот класс позволяет вам видеть, что вызывается, и когда:

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

Когда существующему объекту присваивается объект его собственного класса

    B = A;

Не обязательно. Этот вид назначения называется копированием, то есть оператор присваивания класса будет вызываться для выполнения поэтапного назначения всех членов данных. Фактическая функция MyClass& operator=(MyClass const&)

Конструктор копирования здесь не вызывается. Это связано с тем, что оператор присваивания ссылается на свой объект, поэтому конструкция копирования не выполняется.

Назначение копирования отличается от инициализации копирования, поскольку инициализация копирования выполняется только тогда, когда объект инициализируется. Например:

T y = x;
  x = y;

Первое выражение инициализирует y копированием x. Он вызывает экземпляр-экземпляр MyClass(MyClass const&).

И как уже упоминалось, x = y - это вызов оператора присваивания.

(Существует также что-то, называемое copy-elison, в результате чего компилятор исключит вызовы для конструктора-копии. скорее всего, использует это).


Если функции принимают в качестве аргумента, переданного по значению, объект класса

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

Это правильно. Обратите внимание, что в С++ 11, если a является значением x, и если MyClass имеет соответствующий конструктор MyClass(MyClass&&), a может перемещаться в параметр.

(Конструктор-копия и конструктор move - это две из созданных по умолчанию компиляторных функций-членов класса. Если вы не предоставите их самостоятельно, компилятор будет щедро делать это для вас в определенных обстоятельствах).суб >


Когда функция возвращает (по значению) объект класса

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

Через оптимизация возвращаемого значения, как упоминалось в некоторых ответах, компилятор может удалить вызов для конструктора-копии. Используя опцию компилятора -fno-elide-constructors, вы можете отключить copy-elison и увидеть, что создатель-копия действительно вызывается в этих ситуациях.

Ответ 3

Ситуация (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) конструктор копирования не может быть вызван: если компилятор не обнаруживает никаких побочных эффектов, тогда он может реализовать оптимизацию возвращаемого значения, чтобы оптимизировать ненужную глубокую копию. С++ 11 формализует это с помощью ссылок rvalue.

Ответ 4

Существует три ситуации, в которых вызывается конструктор копирования: Когда мы делаем копию объекта. Когда мы передаем объект в качестве аргумента по значению методу. Когда мы возвращаем объект из метода по значению.

это единственные ситуации.... я думаю...

Ответ 5

Это в основном правильно (кроме вашей опечатки в # 1).

Один дополнительный конкретный сценарий, который следует учитывать, - это когда вы имеете элементы в контейнере, элементы могут копироваться в разное время (например, в векторе, когда вектор растет или некоторые элементы удаляются). Это на самом деле просто пример # 1, но об этом легко забыть.

Ответ 6

Ниже перечислены случаи, когда вызывается конструктор копирования.

  • При создании экземпляра одного объекта и его инициализации значениями из другого объекта.
  • При передаче объекта по значению.
  • Когда объект возвращается из функции по значению.

Ответ 7

Другие предоставили хорошие ответы с пояснениями и ссылками.

Кроме того, я написал класс для проверки различных типов экземпляров /assigments (готовый С++ 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
Переместить вызовы конструктора: 3
Копирование вызовов для звонков: 1
Перемещение вызовов вызова: 2
Деструкторные вызовы: 11

Всего мнений: 15
Всего разрушений: 11
Текущие экземпляры: 4

Здесь - полный код, запущенный на ideone.