繁体   English   中英

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

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

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

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 ++中的类构造函数应通过引用传递对象以避免这种重复副本?

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

explicit CopyAbleComposer(const CopyAble & m1)

值传递和关联的复制是C ++的一个众所周知的属性。 实际上,在过去,C ++因这种无偿复制而受到批评,这种无声复制是很难避免的,并且可能导致性能降低。 这是幽默提到的,例如在这里

您不小心创建了一打自己的实例,然后用脚射击。 提供紧急医疗援助是不可能的,因为您无法分辨出哪些是按位复制的,哪些是指向他人的,并说:“那是我,那边。”

C ++ 98

当声明任何函数/方法按值接收参数时,就会发生这种复制。 它是构造函数,“独立”函数还是方法都没有关系。 为了避免这种情况,请使用const引用:

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

注意:即使您按照以下方式重新排列代码,也始终保留一份副本。 长期以来,这一直是C ++的主要缺陷。

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

C ++ 11

C ++ 11引入了移动语义 ,该语义通过“移动”操作代替了附加副本,该操作比复制更为有效:在通常情况下,对象动态分配内存时,“移动”仅重新分配一些指针,而“复制”分配和取消分配内存。

要从移动语义提供的优化中受益,您应该撤消对C ++ 98所做的“优化”,并按值传递参数。 另外,在初始化mem1成员时,应调用move构造函数:

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

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

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

然后,您应该看到“复制”消息没有出现,而是由“移动”消息代替。

有关更多详细信息,请参见此问题


注意:在所有这些示例中,假定CopyAble对象要复杂得多,并且copy和move构造函数执行的任务很简单(通常是资源管理)。 在现代C ++中,在关注点分离的背景下,资源管理被视为单独的关注 也就是说,任何需要非默认副本或移动构造函数的类都应尽可能小。 这也称为零规则

暂无
暂无

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

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