【问题标题】:Why does the class reference constructor overload get called from an rvalue when there's an rvalue constructor available?当有可用的右值构造函数时,为什么从右值调用类引用构造函数重载?
【发布时间】:2020-09-20 19:11:37
【问题描述】:

这段代码

#include <iostream>

struct A
{
    A(int i) {std::cout << "int received\n";}
    A(A& a) {std::cout << "ref received\n";}
};

int main()
{
    int j = 5;
    A a = j;
}

意外抛出以下编译器错误:

error: invalid initialization of non-const reference of type 'A&' from an rvalue of type 'A'
note:   initializing argument 1 of 'A::A(A&)'
note:   after user-defined conversion: A::A(int)

当我删除第二个构造函数重载A(A&amp; a) 时,一切都按预期工作。我想编译器错误地调用了第二个构造函数而不是第一个。

为什么会这样?

我怎样才能让一个具有引用构造函数和右值构造函数的类协调工作?

我使用 GNU GCC。

注意:我还注意到一些奇怪的事情:显然,如果我将行 A a = j; 替换为 A a(j);,一切都会按预期工作。然而,这并不令人满意,因为如果我尝试从函数参数初始化对象(例如:使用 f(j) 调用 void f(A a)),它仍然不起作用。

【问题讨论】:

  • 隐式转换似乎是这里的关键。我敢打赌,适当地遵循 0/3/5 的规则会解决这个问题!
  • 是时候了解the value categories of C++了。右值(如通过j 的转换创建的临时对象)不能像A(A&amp;) 构造函数所期望的那样绑定到左值引用。您需要使用对 constant 对象的引用才能使其工作,如A(A const&amp;)
  • 有趣的例子。去展示 C++ 是如何演变的。这从无效的 C++14 变为有效的 C++17。
  • 术语:A(A&amp;) 是一个复制构造函数。这不是最常见的形式。 A(const A&amp;) 是您几乎总是会看到的。但它仍然是一个复制构造函数。

标签: c++ initialization copy-constructor copy-initialization


【解决方案1】:

A a = j; 执行copy initialization

直到 C++17,

如果T 是类类型,并且other 的类型的cv 非限定版本不是T 或派生自T,或者如果T 是非类类型,但是other 的类型是类类型,user-defined conversion sequences 可以从 other 的类型转换为 T(或者如果 T 是类类型并且转换函数为从 T 派生的类型可用)进行检查,并通过重载决议选择最好的。如果使用了converting constructor,则转换结果为prvalue temporary (until C++17)prvalue expression (since C++17),然后用于direct-initialize 对象。 The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)

A 类有一个复制构造函数,将左值引用指向非 const,它不能绑定到从 int 转换的临时变量。即使是临时 A 的构造也可能会被优化,复制构造函数必须可用。

使复制构造函数采用左值引用const(或添加移动构造函数)将解决问题。

由于mandatory copy elision,从 C++17 开始,代码可以正常工作。

对象直接构建到存储中,否则它们将被复制/移动到。复制/移动构造函数不需要存在或可访问:

LIVE

另一方面,A a(j); 执行direct initializationa 直接从j 初始化,复制构造函数不参与。

【讨论】:

  • 如果您想参考标准而不是/除了(因为从标准演变的角度来看这是一个非常有趣的例子)cppreference,相关部分是 N4140 中的[class.copy]/32(C++14 + 编辑修复)并根据P0135R1 更改为 C++17(部分)。不过,我找不到完全删除 [class.copy]/32 的论文。
  • @dfri 谢谢。选择 [class.copy]/32 似乎侧重于 return 语句的两阶段重载解决方案,我认为应该有更适合这种情况的解决方案,但我目前在标准中找不到..
  • 是的,你是对的;我自己似乎找不到相关部分(它可能分散在标准的不同部分中,并在 cppreference 中进行了整齐的总结)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-09
  • 2012-07-19
  • 1970-01-01
相关资源
最近更新 更多