为什么在这段代码中没有调用复制构造函数

Why is copy constructor not being called in this code

本文关键字:调用 构造函数 复制 代码 段代码 为什么      更新时间:2023-10-16

为什么在">const Integer运算符+(const Integer&rv("函数中没有调用Copy构造函数。是因为RVO吗如果是,我需要做些什么来防止它

#include <iostream>
using namespace std;
class Integer {
    int i;
public:
    Integer(int ii = 0) : i(ii) {
        cout << "Integer()" << endl;
    }
    Integer(const Integer &I) {
        cout << "Integer(const Integer &)" << endl;
    }
    ~Integer() {
        cout << "~Integer()" << endl;
    }
    const Integer operator+(const Integer &rv) const {
        cout << "operator+" << endl;
        Integer I(i + rv.i);
        I.print();
        return I;
    }
    Integer &operator+=(const Integer &rv) {
        cout << "operator+=" << endl;
        i + rv.i;
        return *this;
    }
    void print() {
        cout << "i: " << i << endl;
    }
};
int main() {
    cout << "built-in tpes:" << endl;
    int i = 1, j = 2, k = 3;
    k += i + j;
    cout << "user-defined types:" << endl;
    Integer ii(1), jj(2), kk(3);
    kk += ii + jj;
}

如果我要注释掉复制构造函数,我确实会得到一个错误。我希望在运算符+返回时调用复制构造函数。以下是程序的输出

built-in tpes:
user-defined types:
Integer()
Integer()
Integer()
operator+
Integer()
i: 3 // EXPECTING Copy Constructor to be called after this
operator+=
~Integer()
~Integer()
~Integer()
~Integer()

是因为RVO吗。如果是,我需要做些什么来防止它?

是的。但由于编译器的返回值优化,它没有被调用。

如果你使用GCC,那么使用-fno-elide-constructors选项来避免它

GCC 4.6.1手册称,

-fno elide构造函数

C++标准允许实现省略创建临时对象,该临时对象仅用于初始化同一类型的另一个对象。指定此选项将禁用该优化,并强制G++在所有情况下调用复制构造函数。

(N(RVO是最容易实现的优化之一。在大多数按值返回的调用约定中,调用者为返回的对象保留空间,然后将隐藏指针传递给函数。然后,函数在给定的地址中构造对象。也就是说,kk += ii + jj;被翻译成类似于:

Integer __tmp;
//                  __rtn  this  arg
Integer::operator+( &tmp,  &ii,  jj );
kk += __tmp;

函数(在这种情况下,Integer::operator+采用第一个隐藏参数__rtn,它是指向要构造对象的sizeof(Integer)字节的未初始化内存块的指针,第二个隐藏参数this,然后是代码中函数的参数。

然后将该功能的实现转换为:

Integer::operator+( Integer* __rtn, Integer const * this, const Integer &rv) {
    cout << "operator+" << endl;
    new (__rtn) Integer(i + rv.i);
    __rtn->print();
}

因为调用约定传递指针,所以函数不需要为随后将被复制的本地整数保留额外的空间,因为它只需将代码中的I直接构建到接收到的指针中,就可以避免复制。

请注意,并不是在所有情况下编译器都可以执行NRVO,特别是如果函数中有两个本地对象,并且根据无法从代码中推断的条件(比如函数的参数值(返回其中一个。虽然你可以这样做来避免RVO,但事实是,它会使你的代码更复杂、效率更低、更难维护。