无法将临时对象作为引用传递

Can't pass temporary object as reference

本文关键字:引用 临时对象      更新时间:2023-10-16

这是一个非常小的例子:

class Foo
{
public:
    Foo(int x) {};
};
void ProcessFoo(Foo& foo)
{
}
int main()
{
    ProcessFoo(Foo(42));
    return 0;
}

上面的内容在Visual Studio上编译得很好,但在Linux和Mac上会生成错误。

编译上述内容会生成以下内容:

$ g++ -std=c++11 -c newfile.cpp
newfile.cpp: In function ‘int main()’:
newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
     ProcessFoo(Foo(42));
                       ^
newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
 void ProcessFoo(Foo& foo)

我找到了三种解决方法:

  1. 为调用 ProcessFoo 创建一个临时变量。

喜欢这个:

Foo foo42(42);
ProcessFoo(foo42);
  1. ProcessFoo采用常量引用:void ProcessFoo(const Foo& foo)

  2. ProcessFoo只是让Foo按值传递。 void ProcessFoo(Foo foo)

为什么编译器禁止我的原始代码?(它防范什么(? 上述三种解决方法中的每一个都满足编译器的要求是什么? MSVC允许什么,但g++不允许?

根据设计,C++只允许将临时传递给 const 引用、值或右值引用。 这个想法是,采用非 const 引用参数的函数声明它想要修改参数并允许它返回到调用方。 临时这样做毫无意义,很可能是一个错误。

而且我不知道你运行的是什么版本的 g++。 它在这里不起作用:http://coliru.stacked-crooked.com/a/43096cb398cbc973

为什么编译器禁止我的原始代码?

因为这是标准所禁止的:

8.5.3 参考文献 5
...
否则,引用应是非易失性常量类型的左值引用(即 cv1 应为 康斯特(,或引用应为右值引用。
[ 示例:
双倍&RD2 = 2.0;错误:不是左值,引用不是常量
...

'

它防范什么?

无意中修改了将在函数调用后销毁的对象。

上述三种解决方法中的每一个都满足编译器的要求是什么?

1 创建命名对象并3副本。
2 之所以有效,是因为可以简单地延长对象的生存期,同时防止对其进行更改。

MSVC允许什么,但g++不允许?

因为它是一种语言扩展。通过转到Property Pages->C/C++->Language->Disable Language Extensions禁用它,您将收到错误。

为什么编译器禁止我的原始代码?

MSVC 有一个扩展,允许临时绑定到非常量左值引用。当然,这不是符合标准的功能,所以我会远离它以方便移植。例如,它不适用于您所看到的最新版本的 GCC 和 Clang。

上述三种解决方法中的每一个都满足编译器的要求是什么?

早在 C++03 年,表达式只能是左值或右值。引用只能指定对象的"左值",因此使用它的目的是为预先存在的对象着色。相比之下,右值在它们出现的表达式之外不存在。此外,引用的最终结果通常是复制或修改对象,并且像55这样的修改右值对语言没有多大意义。

这些规则允许您将右值绑定到常量值的左值引用,在这种情况下,临时的生存期将延长到引用的生存期。当您按值获取对象时,将复制该对象。

对于 C++11,我们有右值引用和 x值,它们是为了交换所有权而制作的。这样一来,左值引用对 const 的用处就会降低。此外,如果按值是右值,则按值会导致移动。

一旦你声明了ProcessFoo的原型

void ProcessFoo(Foo& foo)

您正在传达您的意图,因为形式参数"foo"可能会被修改,因为它不是由 const & 传递的。

在呼叫现场,

ProcessFoo(Foo(42));

Foo(42( 正在创建一个不可修改的临时堆栈对象。可以按值传递或按引用传递到常量方法。

正如您自己列出的那样,满足这些约束会使编译器满意。

  1. 为您提供一个不是编译器生成的对象,并且由您控制。
  2. 通知编译器该方法保证对象的恒定性。
  3. 通知编译器此(临时(对象是按值传递的,因此没有问题。