3

我正在尝试重新学习 C++,并且我倾向于对一切工作原理有一个错综复杂的理解(而不仅仅是“我该怎么做”)。所以我想知道为什么这会产生错误。是的,我知道重载的赋值运算符应该使用引用(如果我这样做的话它工作得很好),但我希望这个问题的答案可以帮助我更多地了解语言规则。

class some_class {
public:
    int n1;
    some_class(int z) : n1(z) { }
    some_class(some_class &x) : n1(x.n1) { }
    some_class operator= (some_class x) { n1 = x.n1; return *this; }
//  some_class & operator= (some_class & x) { n1 = x.n1; return *this; } works fine
};

main () {
    some_class a(10);
    some_class b(20);
    some_class c(30);
    c = b = a;          // error here
}

编译器(C++ 03)在线上给了我这个c = b = a

In function 'int main()':
   error: no matching function for call to 'some_class::some_class(some_class)'
   note: candidates are: some_class::some_class(some_class&)
   note:                 some_class::some_class(int)
   error:   initializing argument 1 of 'some_class some_class::operator=(some_class)'

这让我感到困惑,因为b = a工作正常,而且它正在寻找一个我在法律上不允许声明的构造函数。我意识到在 中c = b = a,该b = a部分返回一个值(不是引用),这可能导致结果被复制到临时文件中。但是为什么不会c = <temporary>导致编译错误b = a呢?知道发生了什么吗?

4

5 回答 5

4

您的复制构造函数有一个非常量引用作为其参数。临时对象不能绑定到非常量引用。当你这样做时:

c = b = a;

这相当于(如你所说):

c.operator=(<temporary>);

因此,它在初始化调用的第一个参数时尝试使用临时调用您的复制构造函数operator=。由于提到的原因,这失败了。解决此问题的明智方法是将签名更改为operator=更常规的:

some_class& operator=(const some_class& x);

然后在 的实现中将不需要复制构造函数,因为不会复制operator=to 的参数。operator=但是,复制构造函数通常应该采用 const 引用参数,因此您还应该将复制构造函数的签名更改为:

some_class(const some_class& x);
于 2013-09-05T17:18:06.610 回答
2

导致错误的原因是您的复制构造函数,它应该是

some_class(const some_class&)

这是因为您不能将临时对象传递给非常量引用,这就是在您的链式赋值中发生的情况。这是因为您的赋值运算符按值返回,这会创建一个临时对象,然后将其作为值参数传递给下一个赋值运算符。这会调用复制构造函数,该构造函数具有非常量引用参数,因此无法绑定到临时对象。

赋值运算符应该是

some_class& operator= (some_class x)

或者

some_class& operator= (const some_class& x)

也就是说,它接受一个相同类型的值或 const 引用参数,并返回一个非常量引用,即*this. 或者,它可以具有 void 返回类型以防止链接。

我知道其他变体是“允许的”,但除非您知道自己在做什么,否则不要使用它们。

除非您有一个值参数的原因(例如复制和交换习惯用法),否则您应该使用 const 引用来防止额外的复制。

于 2013-09-05T17:21:42.637 回答
0

如果我们不使用引用,关键问题还在于不必要的数据副本。

考虑到您正在尝试重新学习 c++,还有另一种方法可以解决这个问题,知道它会很有趣。C++11 引入了 r-value 引用和 std::move 以允许在复制构造函数和赋值运算符中使用临时变量以减少内存副本。

移动复制构造函数和赋值运算符看起来像

some_class(some_class &&x) : n1(std::move(x.n1)) {}
some_class& operator=(some_class&& x) { n1 = std::move(x.n1); return *this; }
于 2013-09-05T17:43:20.103 回答
0

在声明中:

c = b = a;

由于赋值运算符具有从右到左的结合性,因此首先执行 b = a。由于赋值运算符返回值,因此调用了复制构造函数。由于临时对象不能被非常量引用引用(正如上面已经提到的 Stuart Golodetz),编译器会寻找 some_class::some_class(some_class x) 类型的构造函数;并抱怨找不到它。

只有将复制构造函数的签名修改为:

some_class(const some_class& x);

正如其他人所提到的,赋值运算符传统上返回一个引用,因为我们可以链接其中的几个。然而,这样设计它们并不是强制性的。

于 2013-09-05T18:10:42.133 回答
0
some_class(some_class &x) : n1(x.n1) { }
some_class operator= (some_class x) { n1 = x.n1; return *this; }

应该

some_class(const some_class &x) : n1(x.n1) { }
some_class& operator=(const some_class& x) { n1 = x.n1; return *this; }

使用您的原始签名,您无法从示例中显示的 const 临时对象复制或分配。

于 2013-09-05T17:17:56.517 回答