C++默认构造函数

3

使用以下代码编译时,我遇到了一个编译错误:

main.cpp: In function âint main()â:
main.cpp:38: error: no matching function for call to âComplex::Complex(Complex)â
main.cpp:22: note: candidates are: Complex::Complex(Complex&)
main.cpp:15: note:                 Complex::Complex(double, double)

但是当我将复制构造函数的参数类型更改为const Complex&时,它可以工作。 我曾经认为默认构造函数将被调用,并使用2个Complex::Complex(2.0, 0.0)创建一个副本,然后使用Complex(2.0. 0)创建一个。这不正确吗?

#include <iostream>
using namespace std;

class Complex {
        double re;
        double im;

public:
        Complex(double re=0, double im=0);
        Complex(Complex& c);
        ~Complex() {};
        void print();
};

Complex::Complex(double re, double im)
{
        cout << "Constructor called with " << re << " " << im << endl;
        this->re = re;
        this->im = im;
}

Complex::Complex(Complex &c)
{
        cout << "Copy constructor called " << endl;
        re = c.re;
        im = c.im;
}


void Complex::print()
{
        cout << "real = " << re << endl;
        cout << "imaginary = " << im << endl;
}

int main()
{
        Complex a = 2;
        a.print();
        Complex b = a;
        b.print();
}

你是如何调用构造函数的? - Argote
如果它能用const工作,那你为什么不坚持使用呢? - Björn Pollex
1
这些错误不会被微软的编译器报告。它按照你的期望执行,即将 Complex a = 2 视为 Complex a(2,0)。其他编译器可能会将其视为 Complex a = temp,其中 temp 被初始化。在这种情况下,复制构造函数就成了问题。缺少 const 修饰符意味着无法保证 temp 的只读属性。 - Kevin A. Naudé
2
@Kevin:微软编译器以无视使用临时变量作为非常量引用参数的规则而闻名。标准编译器应该只有在声明接受const引用的复制构造函数时才能编译代码。一旦调用复制构造函数的能力得到验证,编译器也可以合法地跳过这个调用,并直接在目标变量中生成值,避免了临时变量。听起来很疯狂吗?欢迎来到C++ :-) - 6502
4个回答

9
当你写代码时,有一些值得注意的事项。
Complex a = 2;

编译器不会直接使用默认参数0来调用Complex构造函数以构建a,而是它将考虑是否可以将2“转换”为Complex。为了进行转换,它将查找你的Complex(re,im)版本,并且由于默认值和未声明显式构造函数,它可能会使用该版本,但这时它将必须找到一种方法将此值传递给a。
用于此“传输”的工具可以是复制构造函数。然而,可以使用Complex(re,im)构建的复杂值是临时的,在C ++中出于某些可疑原因,您不能将临时变量作为非const引用传递给函数。
因此,您的复制构造函数无法与临时变量一起使用,编译器卡住了,因为没有办法使用2初始化a。
如果您声明复制构造函数,而接受一个const引用,则可以将临时变量传递给复制构造函数以初始化a,从而一切正常。
直接初始化a可以使用语法Complex a(2),在这种情况下不需要使用复制构造函数。
还要注意的是,当使用Complex a = ...语法时,编译器必须检查是否可以使用复制构造函数,但一旦已检查完合法性,它就可以不调用它并使用直接初始化代替。换句话说,即使需要声明接受const引用的复制构造函数才能编译,编译器仍然可能跳过该部分,并直接构建a而不调用复制构造函数(即使复制构造函数 - 如在您的情况下 - 具有副作用)。这个看似疯狂的规则已经被添加以允许生成的代码中进行一些优化。

2
为了完整起见,可能应该注意直接(())初始化和复制(=)初始化之间的差异:对于直接初始化,不使用复制构造函数(除非参数与正在初始化的对象类型相同,显然),因此可用性检查不会发生。如果我们说Complex a(2);而不是Complex a = 2;,那就没有问题了。 - James McNellis
2
终于有一个能解释为什么在这种情况下需要“const”的答案了! - Troubadour
非常感谢您的快速回答。我知道默认的复制构造函数会将const引用作为参数,但是我想了解当我写Complex a = 2时,是否会调用构造函数或复制构造函数。我期望会调用默认构造函数,然后调用复制构造函数。因此,当我修复复制构造函数的参数类型时,代码编译成功,并且运行时打印出默认构造函数的消息,但我没有看到“调用复制构造函数”的消息。它是否使用默认赋值运算符? - ap2014
@James McNellis:我在最后添加了那个解释,包括一个警告,即一旦验证了复制构造合法性,实际的复制构造调用可以被跳过。 - 6502
我已经点赞了这个答案,因为它是正确的并且提供了很好的解释,但是这些补充对我来说看起来也不错! - James McNellis
显示剩余2条评论

1

使用&始终创建一个带有copy构造函数,这样你就不需要复制整个对象给函数使用。创建一个对象的副本需要时间,而引用它则更加高效。

无论如何,需要在你的对象参数之前加上const,以确保copy构造函数改变输入对象。例如:

Complex::Complex(const Complex &c)
{
        cout << "Copy constructor called " << endl;
        re = c.re;
        im = c.im;
        c.im = 'something'; // This would not work
}

祝好!
丹尼斯 M.


抱歉,在此评论之前我刚刚更新了几秒钟,选择了错误的单词。 - RageD
看起来我错了,请查看@templatetypedef的回答评论。 - Björn Pollex

1

C++中的复制构造函数需要参数中的const部分以接受常量参数,正如您所发现的那样。否则,您将创建一个无法接受常量参数的复制构造函数,而是创建了一个以非常量Complex&作为参数的复制构造函数。


一个以T&为参数的T类构造函数确实是一个拷贝构造函数。 - James McNellis

1
代码的问题在于你的复制构造函数被定义为这个签名:
Complex::Complex(Complex &c)

这个函数以非const引用作为参数,这可能不是你想要的。例如,这意味着如果你尝试使用复制构造函数复制一个Complex对象,你将被允许修改原始对象!

要解决这个问题,请将你的代码更改为通过const引用来获取Complex

Complex::Complex(const Complex &c)

更一般地说,复制构造函数和赋值运算符应该总是通过const引用接收它们的参数,除非你有非常强烈的理由认为不这样做。

在您的代码中还有一些其他需要指出的事情。首先,在这种情况下,您的复制构造函数是不必要的,因为它只是直接复制了所有字段。有一个经验法则叫做“三大法则”,它说如果你有一个析构函数(那么你也应该有一个赋值运算符),否则编译器提供的默认函数可能已经足够了。

此外,除非您绝对必须使用,否则没有理由编写自己的Complex类。 <complex>头文件定义了complex<T>作为库类。


再次强调,复制构造函数必须通过常量引用方式传递它们的参数。 - Björn Pollex
@templatetypedef:从技术上讲,一个接受非const引用的构造函数并不是一个拷贝构造函数。它是一个转换构造函数。因为根据标准,拷贝构造函数有一个特定的签名。 - John Dibling
1
@John:C++03 12.8/2 表示:“如果类 X 的非模板构造函数的第一个参数为 X&const X&volatile X&const volatile X&,并且要么没有其他参数,要么所有其他参数都有默认参数,则它是一个复制构造函数。” - James McNellis
@Space_C0wb0y,@John:“类X的复制构造函数是一个带有类型为X&或const X&的第一个参数的构造函数” C ++ 03,§12.1p10。 - Fred Nurk
1
@James:你之前的评论已经消失了,我不知道为什么会这样。如果确实是一个复制构造函数,那为什么它没有被调用呢? - Björn Pollex
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接