简体   繁体   English

C ++复制构造函数对成员初始化的双重调用

[英]C++ copy constructor double call on member initialization

Consider the below code, where a composing class with another class as its member is being instantiated: 考虑下面的代码,其中正在实例化一个以另一个类作为其成员的组成类:

class CopyAble {
private:
    int mem1;
public:
    CopyAble(int n1) : mem1(n1) {
        cout << "Inside the CopyAble constructor" << endl;
    }
    CopyAble(const CopyAble& obj) {
        cout << "Inside the CopyAble copy constructor" << endl;
        this->mem1 = obj.mem1;
        return *this;     
    }

    CopyAble& operator=(const CopyAble& obj) {
        cout << "Inside the  CopyAble assignment constructor" << endl;
        this->mem1 = obj.mem1;
    }
    ~CopyAble() {};
};

class CopyAbleComposer {
    private:
        CopyAble memObj;
    public:
        CopyAbleComposer(CopyAble m1) : memObj(m1) {
            cout << "Composing the composer" << endl;
        }
        ~CopyAbleComposer() {}
};

int main()
{
    CopyAble ca(10);
    CopyAbleComposer cac(ca);
    return 0;
}

When I run this, I get the output: 运行此命令时,将得到输出:

Inside the CopyAble constructor  
Inside the CopyAble copy constructor  
Inside the CopyAble copy constructor  
Composing the composer  

Which means that the CopyAble copy constructor is being run twice - once when the CopyAble object is passed into the CopyAbleComposer constructor, and again when the initializer memObj(m1) runs. 这意味着该CopyAble复制构造函数要运行两次 -一次是将CopyAble对象传递到CopyAbleComposer构造函数中,另一次是运行初始化程序memObj(m1)时。

Is this an idiomatic use of the copy constructor? 这是复制构造函数的惯用用法吗? It seems very inefficient that the copy constructor runs twice when we try to initialize a member object with a passed-in object of the same type, and it seems like a trap a lot of C++ programmers can easily fall into without realizing it. 当我们尝试使用相同类型的传入对象初始化成员对象时,复制构造函数运行两次是非常低效的,而且似乎很多C ++程序员很容易陷入陷阱而没有意识到这一点。

EDIT: I don't think this is a duplicate of the question regarding passing a reference into the copy constructor. 编辑:我不认为这是关于将引用传递给副本构造函数的问题的重复。 Here, we are being forced to pass a reference into a regular constructor to avoid duplicate object creation, my question was that is this generally known that class constructors in C++ should have objects passed in by reference to avoid this kind of duplicate copy? 在这里,我们被迫将引用传递给常规构造函数以避免重复对象的创建,我的问题是这是否众所周知,C ++中的类构造函数应通过引用传递对象以避免这种重复副本?

You should accept CopyAble by reference at CopyAbleComposer(CopyAble m1) , otherwise a copy constructor will be called to construct an argument. 您应该在CopyAbleComposer(CopyAble m1)处通过引用接受CopyAble ,否则将调用复制构造函数来构造参数。 You should also mark it as explicit to avoid accidental invocations: 您还应该将其标记为explicit以避免意外调用:

explicit CopyAbleComposer(const CopyAble & m1)

Pass-by-value and the associated copying is a pretty widely known property of C++. 值传递和关联的复制是C ++的一个众所周知的属性。 Actually, in the past C++ was criticized for this gratuitious copying, which happened silently, was hard to avoid and could lead to decreased performance. 实际上,在过去,C ++因这种无偿复制而受到批评,这种无声复制是很难避免的,并且可能导致性能降低。 This is humorously mentioned eg here : 这是幽默提到的,例如在这里

You accidentally create a dozen instances of yourself and shoot them all in the foot. 您不小心创建了一打自己的实例,然后用脚射击。 Providing emergency medical assistance is impossible since you can't tell which are bitwise copies and which are just pointing at others and saying, "That's me, over there." 提供紧急医疗援助是不可能的,因为您无法分辨出哪些是按位复制的,哪些是指向他人的,并说:“那是我,那边。”

C++98 C ++ 98

When any function/method is declared to receive an argument by value, this sort of copying happens. 当声明任何函数/方法按值接收参数时,就会发生这种复制。 It doesn't matter if it's a constructor, a "stand-alone" function or a method. 它是构造函数,“独立”函数还是方法都没有关系。 To avoid this, use a const reference: 为了避免这种情况,请使用const引用:

CopyAbleComposer(const CopyAble& m1) : memObj(m1)
{
    ...
}

Note: even if you rearrange your code as below, one copy always remains. 注意:即使您按照以下方式重新排列代码,也始终保留一份副本。 This has been a major deficiency in C++ for a long time. 长期以来,这一直是C ++的主要缺陷。

CopyAbleComposer cac(CopyAble(10)); // initializing mem1 by a temporary object

C++11 C ++ 11

C++11 introduced move semantics , which replaces the additional copy by a "move" operation, which is supposed to be more efficient than copy: in the common case where an object allocates memory dynamically, "move" only reassigns some pointers, while "copy" allocates and deallocates memory. C ++ 11引入了移动语义 ,该语义通过“移动”操作代替了附加副本,该操作比复制更为有效:在通常情况下,对象动态分配内存时,“移动”仅重新分配一些指针,而“复制”分配和取消分配内存。

To benefit from optimization offered by move semantics, you should undo the "optimization" you maybe did for C++98, and pass arguments by value. 要从移动语义提供的优化中受益,您应该撤消对C ++ 98所做的“优化”,并按值传递参数。 In addition, when initializing the mem1 member, you should invoke the move constructor: 另外,在初始化mem1成员时,应调用move构造函数:

    CopyAbleComposer(CopyAble m1) : memObj(std::move(m1)) {
        cout << "Composing the composer" << endl;
    }

Finally, you should implement the move constructor: 最后,您应该实现move构造函数:

CopyAble(CopyAble&& obj) {
    cout << "Inside the CopyAble move constructor" << endl;
    this->mem1 = obj.mem1;
}

Then you should see that the "copy" message doesn't appear, and is replaced by the "move" message. 然后,您应该看到“复制”消息没有出现,而是由“移动”消息代替。

See this question for more details. 有关更多详细信息,请参见此问题


Note: In all these examples, the CopyAble objects are assumed to be much more complex, with copy and move constructors doing non-trivial work (typically, resource management). 注意:在所有这些示例中,假定CopyAble对象要复杂得多,并且copy和move构造函数执行的任务很简单(通常是资源管理)。 In modern C++, resource management is considered a separate concern , in the context of separation of concerns . 在现代C ++中,在关注点分离的背景下,资源管理被视为单独的关注 That is, any class that needs a non-default copy or move constructor, should be as small as possible. 也就是说,任何需要非默认副本或移动构造函数的类都应尽可能小。 This is also called the Rule of Zero . 这也称为零规则

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

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