В каких ситуациях называется конструктор копии С++?
Я знаю следующие ситуации в С++, где будет вызываться конструктор копирования:
-
когда существующему объекту присваивается объект собственного класса
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
}
Пожалуйста, не стесняйтесь исправить любые ошибки, которые я сделал; но мне более любопытно, есть ли другие ситуации, в которых вызывается конструктор копирования.
Ответы
Ответ 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.