2014-01-18 71 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上運行的完整代碼。