这是一个非常小的例子:
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)
我找到了三种解决方法:
像这样:
Foo foo42(42);
ProcessFoo(foo42);
ProcessFoo 采用 const 引用:
void ProcessFoo(const Foo& foo)
ProcessFoo 只是让 Foo 按值传递。
void ProcessFoo(Foo foo)
为什么编译器禁止我的原始代码? (它防范什么)? 上述三种解决方法中的每一种都满足编译器的要求是什么? MSVC 允许什么,但 g++ 不允许?
根据设计,C++ 只允许将临时值传递给 const 引用、值或右值引用。 这个想法是,一个采用非常量引用参数的函数表明它想要修改该参数并允许它返回到调用者。 临时这样做是没有意义的,而且很可能是一个错误。
我不知道你正在运行什么版本的 g++。 它在这里不起作用:http://coliru.stacked-crooked.com/a/43096cb398cbc973
为什么编译器禁止我的原始代码?
因为标准禁止:
8.5.3 参考文献5
...
否则,该引用应为对非易失性 const 类型的左值引用(即 cv1 应为 const),或者引用应该是右值引用。
[ 示例:
双& rd2 = 2.0; // 错误:不是左值且引用不是 const
...
'
它是在防范什么?
无意中修改了函数调用后将要销毁的对象。
上述三种解决方法中的每一种都满足编译器的要求吗?
1
创建一个命名对象和 3
一个副本。2
之所以有效,是因为对象的生命周期被简单地延长,并且同时防止对其进行更改。
MSVC 允许什么,但 g++ 不允许?
因为它是语言的扩展。通过转到
Property Pages->C/C++->Language->Disable Language Extensions
禁用它,您会收到错误消息。
为什么编译器禁止我的原始代码?
MSVC 有一个扩展,允许临时变量绑定到非常量左值引用。当然,这不是一个符合标准的功能,所以我会远离它以方便携带。例如,正如您所见,它不适用于最新版本的 GCC 和 Clang。
上述三种解决方法中,编译器满意的是什么?
回到 C++03,表达式只能是左值或右值。引用只能指定对象的“左值”,因此使用它的目的是为预先存在的对象起别名。相比之下,右值不存在于它们出现的表达式之外。此外,引用的最终结果通常是复制或修改对象,对于语言来说,修改像
55
这样的右值没有多大意义。
这些规则允许您将右值绑定到 const 的左值引用,在这种情况下,临时的生命周期将延长到引用的生命周期。当您按值获取对象时,该对象将被复制。
在 C++11 中,我们有右值引用和 xvalue,它们是为了交换所有权而创建的。这样就减少了 const 左值引用的用处。此外,如果它是右值,则按值获取会导致移动。
一旦您将 ProcessFoo 的原型声明为
void ProcessFoo(Foo& foo)
您正在传达您的意图,因为形式参数“foo”可能会被修改,因为它不是由 const & 传递的。
在呼叫现场,
ProcessFoo(Foo(42));
Foo(42) 正在创建一个不可修改的临时堆栈对象。可以将值传递或将引用传递给常量到方法。
正如您所列出的,满足这些约束会让编译器满意。
如果您确实想要传递对临时对象的引用(并且存在有合法的情况,其中约束组合导致您需要执行此操作),您可以使用一个简单的模板函数来显式绕过标准:
template<class T>
T& leave(T &&t) { return t; }
class C {};
void Sink(C&);
void Test() {
// Sink(leave(C{})); // <- won't compile
Sink(leave(C{})); // <- compiles
}
这利用了这样一个事实:右值引用可以绑定到临时变量,但它们本身不会导致移动,或者忽略重载解析,实际上可以做比左值引用更多的事情。