2014-01-18 73 views
24

我知道的,其中的复制构造函数被调用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 
        } 
    

请随时纠正我犯任何错误;但我更加好奇,如果有任何其他情况下复制构造函数被调用。

+9

我以为'A = B;'调用复制赋值操作符。 – BWG

+5

另请阅读返回值优化(RVO),您的最后一个示例可能不会复制任何内容。 – Mat

+10

另外,'A = new MyClass();'不会编译。 –

回答

16

我可能是错关于这一点,但这个类可以让你看到什么叫当:

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

还有一个'ac = b;'也调用复制构造函数 – prajmus

+1

不要忘记按参数值传递对象,或按值返回对象。 –

+2

我的代码并不是要展示所有可能的事件,它显示了一个可以用来查看事件的类。 – BWG

5

有3种情况下调用复制构造函数: 当我们复制一个对象时。 当我们将一个对象作为参数传递给一个方法时。 当我们通过一个方法返回一个对象的值。

只有这些情况....我想...

3

以下是情况下,当拷贝构造函数被调用。

  1. 当实例化一个对象并用另一个对象的值初始化它时。
  2. 按值传递对象时。
  3. 通过值从函数返回对象时。
+2

你刚才重复了这个问题的内容。答案应该是“不”。 –

+0

你可以包括每个案例的代码示例吗? – Pandrei

10

情况(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用右值引用形式化了这个。

+0

感谢您指出,我纠正了我的帖子 – Pandrei

5

这基本上是正确的(除了#1中的错字)。

另外一个需要注意的特殊情况是,当容器中有元素时,元素可能会在不同时间被复制(例如,在向量中,当向量增长或某些元素被移除时)。这实际上只是#1的一个例子,但可能很容易忘记它。

16

当现有对象被分配的一个对象自己的类

B = A; 

不一定。这种赋值被称为复制分配,这意味着该类的赋值运算符将被调用来执行所有数据成员的成员赋值。实际功能是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具有相应的构造函数,a则可以将moved转换为参数。 (复制构造函数和移动构造函数是类的默认编译器生成的两个成员函数,如果你自己不提供它们,编译器会在特定情况下为你慷慨地提供这些函数)。


当一个函数返回(由值)类

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

通过return-value optimization的目的,如在一些问题的答案提到的,编译器可以除去该呼叫到复制构造函数。通过使用编译器选项-fno-elide-constructors,可以禁用copy-elison并查看在这些情况下确实会调用复制构造函数。

+0

我不认为最后一个例子是真实的。 “return temp”不会调用复制构造函数,但如果添加“MyClass&ref = temp;”和“return ref;”,这次复制构造函数将被调用。 – chenlian

+1

@chenlian现在我回到这个答案,我发现它有点不准确。如果'-fno-elide-constructors'没有被启用,那么它实际上是* move-constructor *,如果它是可用的,并且如果不是,则调用copy-constructor。原因'MyClass&ref = temp; return ref'调用copy-constructor是因为返回值优化需要一个id表达式。在这种情况下,你需要一个明确的'std :: move'。 – 0x499602D2

2

其他人提供了很好的答案,解释和参考。

此外,我已经写了类来检查广泛的测试中的不同类型instantations/assigments(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
复印分配新建分配FY调用:1个
移动分配新建分配FY调用:2
析构函数调用:19个

总instantations:23
总残害:19个
当前活着的实例:4

最后与复制省略相同的测试启用:

普通构造函数调用:10
拷贝构造函数调用:2
移动构造函数调用:3
复制分配新建分配FY要求:1个
移动分配新建分配FY来电:2
析构函数调用:11个

总instantations:15个
总残害:11个
当前活着的实例:4

Here是在ideone上运行的完整代码。