【问题标题】:Value parameters to operator= cause strange compilation erroroperator= 的值参数导致奇怪的编译错误
【发布时间】:2013-09-05 17:14:23
【问题描述】:

我正在尝试重新学习 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 不会呢?知道发生了什么吗?

【问题讨论】:

  • @user93353 没关系...
  • b = a 不会导致编译错误,因为 a 不是临时对象,并且赋值返回的临时对象被丢弃。

标签: c++


【解决方案1】:

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

c = b = a;

这相当于(如你所说):

c.operator=(<temporary>);

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

some_class& operator=(const some_class& x);

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

some_class(const some_class& x);

【讨论】:

  • 如果您使用的是复制和交换习语,那么您想将值传递给赋值运算符,所以我认为说“正确的方法”是不正确的。
  • @GuyGreer 我会说您可能希望复制赋值运算符按值取值,但可能并非总是如此,因为存在妥协(参见例如stackoverflow.com/questions/18631374/… )。现在,我同意这两者都不是“正确的方法”:)
  • @GuyGreer:谢谢,措辞太强了——我已经改了。
【解决方案2】:

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

some_class(const some_class&amp;)

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

赋值运算符应该是

some_class&amp; operator= (some_class x)

some_class&amp; operator= (const some_class&amp; x)

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

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

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

【讨论】:

  • @ajb 不,我只是浏览了一下。对不起,我的错!
【解决方案3】:
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 临时对象复制或分配。

【讨论】:

  • 还是不对,返回应该是非常量引用。
  • @NeilKirk 这不是重点,请参阅我原来的问题。
  • @ajb 如果使用值副本或引用,我会说这很重要。
  • @ajb 我找到了你的错误原因并编辑了我的答案。
【解决方案4】:

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

鉴于您正在尝试重新学习 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; }

【讨论】:

    【解决方案5】:

    在声明中:

    c = b = a;
    

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

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

    some_class(const some_class& x);
    

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

    【讨论】:

      猜你喜欢
      • 2017-05-24
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多