简体   繁体   English

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

[英]In which situations is the C++ copy constructor called?

I know of the following situations in c++ where the copy constructor would be invoked: 我知道c ++中的以下情况,其中将调用复制构造函数:

  1. when an existing object is assigned an object of it own class 当为现有对象分配其自己的类的对象时

     MyClass A,B; A = new MyClass(); B=A; //copy constructor called 
  2. if a functions receives as argument, passed by value, an object of a class 如果函数接收作为参数,按值传递,则为类的对象

     void foo(MyClass a); foo(a); //copy constructor invoked 
  3. when a function returns (by value) an object of the class 当函数返回(按值)类的对象时

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

Please feel free to correct any mistakes I've made; 请随时纠正我所犯的任何错误; but I am more curious if there are any other situations in which the copy constructor is called. 但是如果有任何其他情况需要调用复制构造函数,我会更好奇。

When an existing object is assigned an object of it own class 当为现有对象分配其自己的类的对象时

  B = A; 

Not necessarily. 不必要。 This kind of assignment is called copy-assignment , meaning the assignment operator of the class will be called to perform memberwise assignment of all the data members. 这种赋值称为复制赋值 ,这意味着将调用类的赋值运算符以执行所有数据成员的成员赋值。 The actual function is MyClass& operator=(MyClass const&) 实际的函数是MyClass& operator=(MyClass const&)

The copy-constructor is not invoked here . 这里不调用copy-constructor This is because the assignment operator takes a reference to its object, and therefore no copy-construction is performed. 这是因为赋值运算符接受对象的引用,因此不执行复制构造。

Copy-assignment is different from copy-initialization because copy-initialization is only done when an object is being initialized. 复制分配与复制初始化不同,因为复制初始化仅在初始化对象时完成。 For example: 例如:

T y = x;
  x = y;

The first expression initializes y by copying x . 第一个表达式通过复制x初始化y It invokes the copy-constructor MyClass(MyClass const&) . 它调用复制构造函数MyClass(MyClass const&)

And as mentioned, x = y is a call to the assignment operator. 如上所述, x = y是对赋值运算符的调用。

(There is also something called copy-elison whereby the compiler will elide calls to the copy-constructor. Your compiler more than likely uses this). (还有一些叫做copy-elison的东西,编译器会忽略对复制构造函数的调用。你的编译器很可能会使用它)。


If a functions receives as argument, passed by value, an object of a class 如果函数接收作为参数,通过值传递,类的对象

  void foo(MyClass a); foo(a); 

This is correct. 这是对的。 However, note that in C++11 if a is an xvalue and if MyClass has the appropriate constructor MyClass(MyClass&&) , a can be moved into the parameter. 然而,请注意,在C ++ 11如果a是一个x值,并且如果MyClass具有适当的构造MyClass(MyClass&&) a移动到参数。

(The copy-constructor and the move-constructor are two of the default compiler-generated member functions of a class. If you do not supply them yourself, the compiler will generously do so for you under specific circumstances). (复制构造函数和移动构造函数是类的默认编译器生成的两个成员函数。如果您自己不提供它们,编译器将在特定情况下慷慨地为您执行此操作)。


When a function returns (by value) an object of the class 当函数返回(按值)类的对象时

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

Through return-value optimization , as mentioned in some of the answers, the compiler can remove the call to the copy-constructor. 通过返回值优化 ,如某些答案中所述,编译器可以删除对copy-constructor的调用。 By using the compiler option -fno-elide-constructors , you can disable copy-elison and see that the copy-constructor would indeed be called in these situations. 通过使用编译器选项-fno-elide-constructors ,您可以禁用copy-elison并查看在这些情况下确实会调用copy-constructor。

I might be wrong about this, but this class allows you to see what is called and when: 我可能错了,但是这个类可以让你看到被调用的内容和时间:

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; 
    };
};

So then this code: 那么这段代码:

a b; //constructor
a c; //constructor
b = c; //copy assignment
c = a(b); //copy constructor, then copy assignment

produces this as the result: 产生这个结果:

constructor called
constructor called
copy assignment operator called
copy constructor called
copy assignment operator called

Another interesting thing, say you have the following code: 另一个有趣的事情,比如你有以下代码:

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

This occurs because when you when you assign a pointer, that does nothing to the actual object. 发生这种情况是因为当您指定指针时,对实际对象不执行任何操作。

Situation (1) is incorrect and does not compile the way you've written it. 情境(1)不正确,不会按照您编写的方式编译。 It should be: 它应该是:

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.

You are correct in case (2). 你是正确的(2)。

But in case (3), the copy constructor may not be called: if the compiler can detect no side effects then it can implement return value optimisation to optimise out the unnecessary deep copy. 但是在情况(3)中,可能不会调用复制构造函数:如果编译器可以检测到没有副作用,那么它可以实现返回值优化以优化不必要的深层复制。 C++11 formalises this with rvalue references . C ++ 11使用右值引用将其形式化。

This is basically correct (other than your typo in #1). 这基本上是正确的(除了#1中的拼写错误)。

One additional specific scenario to watch out for is when you have elements in a container, the elements may be copied at various times (for example, in a vector, when the vector grows or some elements are removed). 需要注意的另一个特定方案是,当容器中有元素时,可以在不同时间复制元素(例如,在向量中,当向量增长或删除某些元素时)。 This is actually just an example of #1, but it can be easy to forget about it. 这实际上只是#1的一个例子,但很容易忘记它。

There are 3 situations in which the copy constructor is called: When we make copy of an object. 有三种情况可以调用复制构造函数:当我们复制一个对象时。 When we pass an object as an argument by value to a method. 当我们通过值将对象作为参数传递给方法时。 When we return an object from a method by value. 当我们按值从方法返回一个对象时。

these are the only situations....i think... 这是唯一的情况......我想......

The following are the cases when copy constructor is called. 以下是调用复制构造函数的情况。

  1. When instantiating one object and initializing it with values from another object. 实例化一个对象并使用另一个对象的值初始化它时。
  2. When passing an object by value. 按值传递对象时。
  3. When an object is returned from a function by value. 按值从函数返回对象时。

Others have provided good answers, with explanations and references. 其他人提供了很好的答案,有解释和参考。

In addition, I have written a class to check the different type of instantations/assigments (C++11 ready), within an extensive test: 另外,我在一个广泛的测试中编写了一个类来检查不同类型的瞬时/分配(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;

Here is the test: 这是测试:

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 );
}

This is an abstract of the test compiled with GCC 4.8.2 with -O3 and -fno-elide-constructors flags: 这是使用GCC 4.8.2使用-O3-fno-elide-constructors标志编译的测试摘要:

Normal constructor calls: 10 普通构造函数调用:10
Copy constructor calls: 2 复制构造函数调用:2
Move constructor calls: 11 移动构造函数调用:11
Copy assigment calls: 1 复制分配电话:1
Move assigment calls: 2 移动分配电话:2
Destructor calls: 19 析构函数:19

Total instantations: 23 总计即时:23
Total destructions: 19 彻底破坏:19
Current alive instances: 4 当前活着的实例:4

Finally the same test with copy elision enabled: 最后启用了复制省略的相同测试:

Normal constructor calls: 10 普通构造函数调用:10
Copy constructor calls: 2 复制构造函数调用:2
Move constructor calls: 3 移动构造函数调用:3
Copy assigment calls: 1 复制分配电话:1
Move assigment calls: 2 移动分配电话:2
Destructor calls: 11 析构函数:11

Total instantations: 15 总计即时:15
Total destructions: 11 总破坏:11
Current alive instances: 4 当前活着的实例:4

Here is the complete code running at ideone. 是在ideone上运行的完整代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM