7

我编写了一个简单的 C++ 类示例,其中包含 1 个非参数构造函数、1 个参数构造函数、2 个复制构造函数、1 个赋值运算符和 1 个加运算符。

class Complex {
protected:
    float real, img;
public:
    Complex () : real(0), img(0) {
        cout << "Default constructor\n";
    }

    Complex (float a, float b) {
        cout << "Param constructor" << a << " " << b << endl;
        real = a;
        img = b;
    }

    // 2 copy constructors
    Complex( const Complex& other ) {
        cout << "1st copy constructor " << other.real << " " << other.img << endl;
        real = other.real;
        img = other.img;
    }

    Complex( Complex& other ) {
        cout << "2nd copy constructor " << other.real << " " << other.img << endl;
        real = other.real;
        img = other.img;
    }

    // assignment overloading operator
    void operator= (const Complex& other) {
        cout << "assignment operator " << other.real << " " << other.img << endl;
        real = other.real;
        img = other.img;
    }

    // plus overloading operator
    Complex operator+ (const Complex& other) {
        cout << "plus operator " << other.real << " " << other.img << endl;
        float a = real + other.real;
        float b = img + other.img;
        return Complex(a, b);
    }

    float getReal () {
        return real;
    }

    float getImg () {
        return img;
    }
};

我在 main 中使用了这个类,就像这样:

int main() {
    Complex a(1,5);
    Complex b(5,7);
    Complex c = a+b; // Statement 1
    system("pause");
    return 0;
}

结果打印为:

Param constructor 1 5
Param constructor 5 7
plus operator 5 7
Param constructor 6 12

我认为 Statement 1 中必须使用复制构造函数,但我真的不知道调用的是哪个。请告诉我是哪一个,为什么?非常感谢

4

1 回答 1

9

编译器正在省略对复制构造函数的调用(实际上是两次调用)。即使构造函数或析构函数有副作用,根据 C++11 标准的第 12.8/31 段,这是允许的(但不是强制的!):

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。[..] 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

— 在return具有类返回类型的函数的语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作

[...]

— 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv-unqualified 类型的类对象时,可以通过将临时对象直接构造到省略复制/移动的目标

如果编译器没有忽略对复制构造函数的调用,那么第一个将被选中两次,即具有以下签名的那个:

Complex( const Complex& other )

原因是:

  • 返回的值operator +是从临时 ( Complex(a, b)) 复制初始化的,并且只有对的左值引用const才能绑定到临时对象。operator +如果这样写,情况会有所不同:

    Complex operator+ (const Complex& other) {
        // ...
        Complex c(a, b);
        return c;
    } 
    

    在这种情况下,将调用第二个复制构造函数,因为c它不是const- 限定的并且是一个左值,所以它可以绑定到对 non- 的左值引用const

  • 对象cinmain()是从右值复制构造的(返回的值operator +也是临时的)。不管如何operator +返回它的Complex对象都是如此,只要它按值返回它。因此,只要不执行复制省略,就会选择第一个复制构造函数。

如果你正在使用 GCC 并想验证这种行为,请尝试设置-fno-elide-constructors编译标志(Clang 也支持它,但 3.2 版本有一个错误,我不知道它是否已修复)。

于 2013-05-19T15:24:42.780 回答