在什么情况下调用 C++ 复制构造函数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21206359/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:32:19  来源:igfitidea点击:

In which situations is the C++ copy constructor called?

c++visual-c++constructorcopy-constructor

提问by Pandrei

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

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.

请随时纠正我犯的任何错误;但是我更好奇是否还有其他调用复制构造函数的情况。

采纳答案by BWG

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.

发生这种情况是因为当您分配指针时,它对实际对象没有任何作用。

回答by 0x499602D2

When an existing object is assigned an object of it own class

    B = A;

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

    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&)

不必要。这种赋值称为copy-assignment,这意味着将调用类的赋值运算符来执行所有数据成员的成员赋值。实际功能是MyClass& operator=(MyClass const&)

The copy-constructor is not invoked here. 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-initializationbecause copy-initialization is only done when an object is being initialized. For example:

复制赋值与复制初始化不同,因为复制初始化仅在对象被初始化时进行。例如:

T y = x;
  x = y;

The first expression initializes yby copying x. It invokes the copy-constructor MyClass(MyClass const&).

第一个表达式y通过复制初始化x。它调用复制构造函数MyClass(MyClass const&)

And as mentioned, x = yis a call to the assignment operator.

如前所述,x = y是对赋值运算符的调用。

(There is also something called copy-elisonwhereby 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);

如果函数接收作为参数,按值传递,一个类的对象

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

This is correct. However, note that in C++11 if ais an xvalue and if MyClasshas the appropriate constructor MyClass(MyClass&&), acan be movedinto the parameter.

这是对的。但是,请注意,在 C++11 中 ifa是 xvalue 并且如果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
    }

当函数返回(按值)类的对象时

    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. 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.

通过return-value optimization,正如一些答案中提到的,编译器可以删除对复制构造函数的调用。通过使用编译器选项-fno-elide-constructors,您可以禁用 copy-elison 并查看在这些情况下确实会调用 copy-constructor。

回答by Bathsheba

Situation (1) is incorrect and does not compile the way you've written it. It should be:

情况 (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.

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 optimisationto optimise out the unnecessary deep copy. C++11 formalises this with rvalue references.

但是在情况(3)中,可能不会调用复制构造函数:如果编译器检测到没有副作用,那么它可以实现返回值优化以优化掉不必要的深复制。C++11 用右值引用形式化了这一点。

回答by Lightness Races in Orbit

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 的一个例子,但很容易忘记它。

回答by Akshay

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.

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

these are the only situations....i think...

这些是唯一的情况......我认为......

回答by leshy84

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.
  1. 实例化一个对象并使用另一个对象的值对其进行初始化时。
  2. 按值传递对象时。
  3. 当一个对象按值从函数返回时。

回答by Manu343726

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 就绪),在一个广泛的测试中:

#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.2with -O3and -fno-elide-constructorsflags:

这是GCC 4.8.2使用-O3-fno-elide-constructors标志编译的测试的摘要:

Normal constructor calls: 10
Copy constructor calls: 2
Move constructor calls: 11
Copy assigment calls: 1
Move assigment calls: 2
Destructor calls: 19

Total instantations: 23
Total destructions: 19
Current alive instances: 4

普通构造函数调用:10
复制构造函数调用:2
移动构造函数调用:11
复制赋值调用:1
移动赋值调用:2
析构函数调用:19

总 实例:23
总破坏:19
当前活着的实例:4

Finally the same test with copy elision enabled:

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

Normal constructor calls: 10
Copy constructor calls: 2
Move constructor calls: 3
Copy assigment calls: 1
Move assigment calls: 2
Destructor calls: 11

Total instantations: 15
Total destructions: 11
Current alive instances: 4

普通构造函数调用:10
复制构造函数调用:2
移动构造函数调用:3
复制赋值调用:1
移动赋值调用:2
析构函数调用:11

总 实例:15
总破坏:11
当前活着的实例:4

Hereis the complete code running at ideone.

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