繁体   English   中英

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

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

我知道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 } 

请随时纠正我所犯的任何错误; 但是如果有任何其他情况需要调用复制构造函数,我会更好奇。

当为现有对象分配其自己的类的对象时

  B = A; 

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

这里不调用copy-constructor 这是因为赋值运算符接受对象的引用,因此不执行复制构造。

复制分配与复制初始化不同,因为复制初始化仅在初始化对象时完成。 例如:

T y = x;
  x = y;

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

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

(还有一些叫做copy-elison的东西,编译器会忽略对复制构造函数的调用。你的编译器很可能会使用它)。


如果函数接收作为参数,通过值传递,类的对象

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

这是对的。 然而,请注意,在C ++ 11如果a是一个x值,并且如果MyClass具有适当的构造MyClass(MyClass&&) a移动到参数。

(复制构造函数和移动构造函数是类的默认编译器生成的两个成员函数。如果您自己不提供它们,编译器将在特定情况下慷慨地为您执行此操作)。


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

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

通过返回值优化 ,如某些答案中所述,编译器可以删除对copy-constructor的调用。 通过使用编译器选项-fno-elide-constructors ,您可以禁用copy-elison并查看在这些情况下确实会调用copy-constructor。

我可能错了,但是这个类可以让你看到被调用的内容和时间:

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

发生这种情况是因为当您指定指针时,对实际对象不执行任何操作。

情境(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使用右值引用将其形式化。

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

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

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

这是唯一的情况......我想......

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

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

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

另外,我在一个广泛的测试中编写了一个类来检查不同类型的瞬时/分配(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

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

暂无
暂无

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

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