在什么情况下调用C ++拷贝构造函数?

时间:2014-01-18 15:54:20

标签: c++ visual-c++ constructor copy-constructor

我知道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
       } 
    
  4. 请随时纠正我所犯的错误;但是如果有任何其他情况需要调用复制构造函数,我会更好奇。

7 个答案:

答案 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)

以下是调用复制构造函数的情况。

  1. 实例化一个对象并使用另一个对象的值初始化它。
  2. 按值传递对象时。
  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上运行的完整代码。