c++ - 为什么显式定义拷贝构造函数后就无法通过编译了?

浏览:53日期:2023-04-06

问题描述

初学C++,遇到了这样一个问题,代码如下:

#include <iostream>using namespace std;class Complex {float real, imag;public: Complex(float r = 0.0, float i = 0.0):real(r), imag(i) {cout << 'Complex constructor called' << endl; } Complex (Complex& complex):real(complex.real), imag(complex.imag) {cout << 'Complex copy constructor called' << endl; } int main() { Complex c3 = 3.14;}

运行的时候会有编译错误,如下:

foobar.cpp:42:13: error: no viable constructor copying variable of type ’Complex’ Complex c3 = 3.14; ^ ~~~~foobar.cpp:10:5: note: candidate constructor not viable: expects an l-value for 1st argument Complex (Complex& complex):real(complex.real), imag(comp... ^

然而,当我删掉我显式定义的拷贝构造函数后,就可以正常编译了,即:

#include <iostream> using namespace std;class Complex { float real, imag; public:Complex(float r = 0.0, float i = 0.0):real(r), imag(i) { cout << 'Complex constructor called' << endl;} int main() {Complex c3 = 3.14; }

这样就可以正常编译,请问这是为什么呢?我用的是 g++ 4.2.1

目前我知道的是:当执行'Complex c3 = 3.14'时发生这些:先调用我显式定义的默认构造函数'Complex(float r = 3.14, float i = 0.0)'创建了一个临时对象,然后利用拷贝构造函数(如果有合法的)去创建c3,然后讲临时对象析构。

其实我的问题有两个:1)为什么默认的拷贝构函数造能通过编译2)为什么我定义的拷贝构函数造不能通过编译

补充:之前的问题已解决,但是现在遇到了新的问题代码如下:

#include <iostream>using namespace std;class Complex { float real, imag;public: Complex(float r = 0.0, float i = 0.0):real(r), imag(i) {cout << 'Complex constructor called' << endl; } Complex(const Complex& complex):real(complex.real), imag(complex.imag) {cout << 'Complex copy constructor called' << endl; }};int main() { Complex c = 3.22;}

可以通过编译,但是输出结果为:

Complex constructor called

并没有调用拷贝构造函数啊,为什么呢?是被编译器优化了吗?如果不需要调用拷贝构造函数,那为什么之前不加const限定的拷贝构造函数不能通过编译呢?我用的编译器是 g++ 4.2.1

问题解答

回答1:

因为C++规范允许编译器在这个时候优化掉不必要的拷贝构造。GCC加上参数-fno-elide-constructors可以不优化。虽然这个调用拷贝构造的过程是后来被优化掉了的。但是编译检查语法的时候仍然需要它存在的。而VC则是直接优化掉,根本不会调用拷贝构造函数。优化了以后,Complex c3 = 3.14;这样的初始化表达式和Complex c3(3.14);是等价的了。

回答2:

Complex c3=3.14;这个拷贝构造的是常量所以你应该将定义改为 Complex (const Complex& complex);加上const关键字后,非常量的变量也可以拷贝构造,所以一般的拷贝构造函数都加上const。希望对你有帮助!

不好意思!我先前的回答有误对于Complex c3=3.14;因为这个3.14不是complex对象,系统应该将其认为float类型对象,所以其调用将相当于:Complex c3(3.14);调用的是你定义的构造函数,而这个过程这是初始化,没有调用到默认的拷贝构造函数。

而拷贝构造函数调用的时机是拷贝相同类的对象的时候,即当拷贝的是Complex对象时才调用。例如:Complex c4=c3;这时才会调用拷贝构造函数

我将你之前的代码在我的电脑上运行时没有发现编译错误,所以我估计是编译器的原因,对于这方面我也不熟悉所以无法为你解答。

希望我的回答对你有所帮助!

回答3:

类中提供拷贝构造函数,就不会再提供默认的无参构造和有参构造,所以删掉拷贝构造就可以正常编译了

回答4:

如果你是贴代码的时候忘了花括号和分号的话,这是 clang 的 bug 吧……

用 Visual C++ 14.0 (VS2015) 可以编译通过的。

#include <iostream>using namespace std;class Complex{ float real, imag;public: Complex(float r = 0.0, float i = 0.0) : real(r), imag(i) {cout << 'Complex constructor called' << endl; } Complex(const Complex& complex) : real(complex.real), imag(complex.imag) // 有没有 const 都可以 {cout << 'Complex copy constructor called' << endl; } friend ostream& operator<<(ostream& os, const Complex& one) {os << '<' << one.real << ', ' << one.imag << '>';return os; }};int main(){ Complex c3 = 3.14; // 怎么搞都可以 Complex c4{1}; // 怎么搞都可以 Complex c5{1, 2}; // 怎么搞都可以 cout << 'c3: ' << c3 << endl; cout << 'c4: ' << c4 << endl; cout << 'c5: ' << c5 << endl; return 0;}

运行结果:

Complex constructor calledComplex constructor calledComplex constructor calledc3: <3.14, 0>c4: <1, 0>c5: <1, 2>

然后对于你添加的问题:

Complex c3 = 3.14;

做的的确是初始化 initialization,而非赋值 assignment。解释在 C++ 之父 Bjarne Stroustrup 的著作 A Tour of C++ 中:

C++ offers a variety of notations for expressing initialization, such as the = used above, and a universal form based on curly-brace-delimited initializer lists:

double d1 = 2.3; // initialize d1 to 2.3double d2 {2.3}; // initialize d2 to 2.3

complex<double> z = 1; // a complex number with double-precision floating-point scalarscomplex<double> z2 {d1,d2};complex<double> z3 = {1,2}; // the = is optional with { ... }vector<int> v {1,2,3,4,5,6}; // a vector of ints

The = form is traditional and dates back to C, but if in doubt, use the general {}-list form.

相关文章: