在哪些情况下调用 C++ 复制构造函数?
Posted
技术标签:
【中文标题】在哪些情况下调用 C++ 复制构造函数?【英文标题】:In which situations is the C++ copy constructor called? 【发布时间】:2014-02-07 23:47:19 【问题描述】:我知道在 c++ 中会调用复制构造函数的以下情况:
当一个现有对象被分配一个它自己的类的对象时
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
请随时纠正我所犯的任何错误;但是我更好奇是否还有其他调用复制构造函数的情况。
【问题讨论】:
我以为A=B;
调用了复制赋值运算符。
另请阅读返回值优化 (RVO),您的最后一个示例可能不会复制任何内容。
另外,A = new MyClass();
不会编译。
这不是有效的 C++。
@BWG,只有在声明A
之后完成。例如:A a; ... a=b;
。如果在声明本身完成,则A a=b
等同于A a(b)
。
【参考方案1】:
当一个现有对象被分配一个它自己的类的对象时
B = A;
不一定。这种赋值称为copy-assignment,意思是调用类的赋值运算符对所有数据成员进行成员赋值。实际功能是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 放入参数中。
(copy-constructor和move-constructor是类的两个默认编译器生成的成员函数。如果你自己不提供,编译器会在特定情况下慷慨地为你提供) .
当函数(按值)返回类的对象时
MyClass foo () MyClass temp; .... return temp; // copy constructor called
通过return-value optimization,正如一些答案中提到的,编译器可以删除对复制构造函数的调用。通过使用编译器选项-fno-elide-constructors
,您可以禁用复制elison,并查看在这些情况下确实会调用复制构造函数。
【讨论】:
我不认为最后一个例子是真的。 “return temp”不会调用复制构造函数,但如果添加“MyClass & ref = temp;”和“return ref;”,这一次将调用复制构造函数。 @chenlian 现在我回到这个答案,我发现它有点不准确。如果-fno-elide-constructors
未启用,则实际上是 move-constructor 如果可用则首先调用它,如果不可用则调用 copy-constructor。 MyClass& ref=temp; return ref
调用复制构造函数的原因是因为返回值优化需要一个 id 表达式。在这种情况下,您需要一个明确的 std::move
。
为-fno-elide-constructors
点赞。没有它,我的一些测试永远无法符合我的假设。【参考方案2】:
其他人提供了很好的答案,并附有解释和参考。
此外,我编写了一个类来检查不同类型的实例化/分配(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 移动构造函数调用:3 复制分配调用:1 移动分配调用:2 析构函数调用:11
总实例数:15 破坏总数:11 当前活跃实例:4
Here 是在 ideone 运行的完整代码。
【讨论】:
【参考方案3】:这基本上是正确的(除了您在#1 中的错字)。
另外一个需要注意的特定情况是,当容器中有元素时,这些元素可能会在不同时间被复制(例如,在向量中,当向量增长或某些元素被删除时)。这实际上只是 #1 的一个示例,但很容易忘记它。
【讨论】:
【参考方案4】:我可能错了,但是这个类可以让你看到什么时候被调用:
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
这是因为当你分配一个指针时,它对实际对象没有任何作用。
【讨论】:
还有一个a c = b;
也调用了拷贝构造函数
不要忘记按值传递对象作为参数,或按值返回对象。
我的代码并不是为了演示所有可能的事件,它显示了一个可以用来查看事件的类。
@Swapnil 我认为它应该是复制赋值运算符,因为您使用的是 = 运算符。据我所知,如果你使用 = 运算符,它总是调用 operator=,除非是第一次初始化。
如果你需要测试向量行为,当你像这样声明复制构造函数(和赋值操作)时,默认情况下不会定义 move 构造函数(和赋值操作)由编译器!因此,在某些情况下,移动构造函数可能比复制更受青睐。但你无法分辨,因为这样复制构造函数总是会被调用。【参考方案5】:
情况 (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 使用 右值引用 将其形式化。
【讨论】:
【参考方案6】:以下是调用复制构造函数的情况。
-
在实例化一个对象并使用来自另一个对象的值对其进行初始化时。
按值传递对象时。
当对象按值从函数返回时。
【讨论】:
你只是重复了问题的内容。答案应该是“不”。【参考方案7】:复制构造函数被调用的三种情况: 当我们复制一个对象时。 当我们通过值将对象作为参数传递给方法时。 当我们从一个方法中按值返回一个对象时。
这些是唯一的情况......我认为......
【讨论】:
以上是关于在哪些情况下调用 C++ 复制构造函数?的主要内容,如果未能解决你的问题,请参考以下文章