我知道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
}
请随时纠正我所犯的错误;但是如果有任何其他情况需要调用复制构造函数,我会更好奇。
答案 0 :(得分:22)
当为现有对象分配其自己的对象
时B = A;
不一定。这种赋值称为 copy-assignment ,这意味着将调用类的赋值运算符以执行所有数据成员的成员赋值。实际功能是MyClass& operator=(MyClass const&)
此处未调用复制构造函数。这是因为赋值运算符接受对象的引用,因此不执行复制构造。
复制分配与复制初始化不同,因为复制初始化仅在初始化对象时完成。例如:
T y = x;
x = y;
第一个表达式通过复制y
来初始化x
。它调用copy-constructor 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,如某些答案中所述,编译器可以删除对copy-constructor的调用。通过使用编译器选项-fno-elide-constructors
,您可以禁用copy-elison并查看在这些情况下确实会调用copy-constructor。
答案 1 :(得分:20)
我可能错了,但是这个课程可以让你看到什么叫做以及什么时候:
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 :(得分:12)
情况(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使用 rvalue references 来形式化它。
答案 3 :(得分:6)
这基本上是正确的(除了#1中的错字)。
需要注意的另一个特定方案是,当容器中有元素时,可以在不同时间复制元素(例如,在向量中,当向量增长或删除某些元素时)。这实际上只是#1的一个例子,但很容易忘记它。
答案 4 :(得分:5)
有三种情况可以调用复制构造函数: 当我们制作一个对象的副本。 当我们通过值将对象作为参数传递给方法时。 当我们按值从方法返回一个对象时。
这是唯一的情况......我想......
答案 5 :(得分:3)
以下是调用复制构造函数的情况。
答案 6 :(得分:2)
其他人提供了很好的答案,附有解释和参考。
另外,我在一个广泛的测试中编写了一个类来检查不同类型的瞬时/分配(C ++ 11 ready):
#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上运行的完整代码。