Home  >  Q&A  >  body text

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

初学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

PHP中文网PHP中文网2715 days ago599

reply all(4)I'll reply

  • PHPz

    PHPz2017-04-17 15:01:27

    Because the C++ specification allows the compiler to optimize out unnecessary copy construction at this time.
    GCC plus parameter -fno-elide-constructors does not need to be optimized.
    Although the process of calling copy construction was later optimized. But it still needs to exist when compiling and checking the syntax. VC is directly optimized and does not call the copy constructor at all.
    After optimization, the initialization expression Complex c3 = 3.14; is equivalent to Complex c3(3.14);.

    reply
    0
  • 天蓬老师

    天蓬老师2017-04-17 15:01:27

    Complex c3=3.14;This copy constructor is a constant
    , so you should change the definition to Complex (const Complex& complex);
    After adding the const keyword, non-const variables can also be copy-constructed, so the general copy constructor All add const.
    Hope this helps!


    Sorry! My previous answer was wrong
    for Complex c3=3.14; because this 3.14 is not a complex object, the system should treat it as a float type object, so its call will be equivalent to:
    Complex c3(3.14);
    The constructor you defined is called, and this process is initialization, and the default copy constructor is not called.

    The copy constructor is called when an object of the same class is copied, that is, it is called when the Complex object is copied.
    For example: Complex c4=c3;Only then will call the copy constructor .

    I found no compilation errors when I ran your previous code on my computer, so I guess it’s the compiler. I’m not familiar with this aspect so I can’t answer it for you.

    I hope my answer will be helpful to you!

    reply
    0
  • 高洛峰

    高洛峰2017-04-17 15:01:27

    If the copy constructor is provided in the class, the default parameterless and parameterized constructors will no longer be provided, so deleting the copy constructor will allow normal compilation

    reply
    0
  • 迷茫

    迷茫2017-04-17 15:01:27

    If you forget the curly braces and semicolons when posting the code, this is a clang bug...

    It can be compiled with 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;
    }

    Run result:

    Complex constructor called
    Complex constructor called
    Complex constructor called
    c3: <3.14, 0>
    c4: <1, 0>
    c5: <1, 2>

    Then for the question you added:

    Complex c3 = 3.14;

    What it does is indeed initialization, not assignment. Explained in A Tour of C++ by Bjarne Stroustrup, the father 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.3
    double d2 {2.3};          // initialize d2 to 2.3
    complex<double> z = 1;            // a complex number with double-precision floating-point scalars
    complex<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.

    reply
    0
  • Cancelreply