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

时间:2017-11-13 16:41:29

标签: c++ constructor

考虑下面的代码,其中实例化了另一个类作为其成员的组合类:

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

当我运行它时,我得到输出:

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

这意味着CopyAble复制构造函数正在运行两次 - 一次将CopyAble对象传递给CopyAbleComposer构造函数时,再次运行初始化程序memObj(m1)时。

这是复制构造函数的惯用法吗?当我们尝试使用相同类型的传入对象初始化成员对象时,复制构造函数运行两次似乎非常低效,并且它似乎是一个陷阱,许多C ++程序员很容易陷入其中而没有意识到它。

编辑:我不认为这是关于将引用传递给复制构造函数的问题的副本。在这里,我们被迫将引用传递给常规构造函数以避免重复对象创建,我的问题是,众所周知,C ++中的类构造函数应该具有通过引用传入的对象以避免这种重复副本吗?

2 个答案:

答案 0 :(得分:2)

您应该在CopyAble处通过引用接受CopyAbleComposer(CopyAble m1),否则将调用复制构造函数来构造参数。您还应将其标记为explicit以避免意外调用:

explicit CopyAbleComposer(const CopyAble & m1)

答案 1 :(得分:2)

传值和相关的复制是C ++的一个广为人知的属性。实际上,在过去,C ++被批评为这种无偿的复制,这种复制是无声的,很难避免并且可能导致性能下降。这是幽默地提到的,例如here

  

你不小心制造了十几个你自己的实例,并用脚射击它们。提供紧急医疗援助是不可能的,因为你无法分辨哪些是按位副本,哪些只是指着别人说,&#34;那是我,那边。&#34;

C ++ 98

当声明任何函数/方法按值接收参数时,会发生这种复制。如果它是一个构造函数,一个独立的&#34;它并不重要。功能或方法。要避免这种情况,请使用const引用:

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

注意:即使您按如下方式重新排列代码,也始终保留一份副本。这在很长一段时间内一直是C ++的一个主要缺陷。

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

C ++ 11

C ++ 11引入了move semantics,用&#34;移动&#34;替换了附加副本。操作,应该比复制更有效:在一个对象动态分配内存的常见情况下,&#34;移动&#34;只重新分配一些指针,而#34; copy&#34;分配和释放内存。

要从移动语义提供的优化中受益,您应撤消&#34;优化&#34;你可能为C ++ 98做了,并按值传递参数。此外,在初始化mem1成员时,您应该调用移动构造函数:

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

最后,您应该实现移动构造函数:

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

然后你应该看到&#34;复制&#34;消息不会出现,并被&#34;移动&#34;消息。

有关详细信息,请参阅this question

注意:在所有这些示例中,假设CopyAble对象要复杂得多,复制和移动构造函数执行非常重要的工作(通常是资源管理)。在现代C ++中,资源管理在separation of concerns的上下文中被视为单独的关注。也就是说,任何需要非默认副本或移动构造函数的类都应该尽可能小。这也称为Rule of Zero