使用g++
class A {};
void foo(A&) {}
int main()
{
foo(A());
return 0;
}
我收到以下错误消息:
> g++ test.cpp -o test
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’
经过一番反思,这些错误对我来说很有意义。 A()
只是一个临时值,而不是堆栈中的可分配位置,所以它似乎没有地址。如果它没有地址,那么我就无法持有它的引用。好的,好的。
但是等等!如果我将以下转换运算符添加到类A
class A
{
public:
operator A&() { return *this; }
};
然后一切都很好!我的问题是这是否远程安全。当this
被构造为临时值时,A()
究竟指向了什么?
让我有信心
void foo(const A&) {}
可以根据g++
和我使用的所有其他编译器接受临时值。 const
关键字总是可以丢弃,所以如果const A&
参数和A&
参数之间存在任何实际的语义差异,我会感到惊讶。所以我猜这是另一种问我问题的方式:为什么编译器认为临时值的const
引用是安全的,而非const
引用不是?
答案 0 :(得分:16)
这不是一个地址不能被采用(编译器可以总是命令它在堆栈上推送它,它与ref-to-const一起),这是程序员意图的问题。有了A&的接口,它说“我将修改此参数中的内容,以便您可以在函数调用后读取”。如果你传递一个临时的,那么它“修改”的东西在函数之后就不存在了。这(可能)是编程错误,所以不允许这样做。例如,考虑:
void plus_one(int & x) { ++x; }
int main() {
int x = 2;
float f = 10.0;
plus_one(x); plus_one(f);
cout << x << endl << f << endl;
}
这不能编译,但是如果temporaries可以绑定到ref-to-non-const,它会编译但会产生令人惊讶的结果。在plus_one(f)中,f将被隐式转换为临时int,plus_one将获取temp并递增它,使底层float f保持不变。当plus_one返回时,它将没有任何效果。这几乎肯定不是程序员想要的。
该规则偶尔会搞乱。一个常见的例子(描述为here),试图打开一个文件,打印一些东西,然后关闭它。你希望能够做到:
ofstream("bar.t") << "flah";
但你不能因为运营商&lt;&lt;采用ref-to-non-const。您的选项可以分为两行,或者调用返回ref-to-non-const的方法:
ofstream("bar.t").flush() << "flah";
答案 1 :(得分:4)
当您为const引用分配r值时,可以保证在销毁引用之前不会销毁临时值。当您指定非const引用时,不会进行此类保证。
int main()
{
const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
A& a1 = A(); // You can't do this.
}
你无法安全地抛弃常识,并希望事情有效。 const和非const引用有不同的语义。
答案 2 :(得分:3)
有些人可能遇到的问题:MSVC编译器(Visual Studio编译器,使用Visual Studio 2008验证)将编译此代码没有任何问题。我们一直在一个项目中使用这个范例,用于通常采用一个参数(一大块数据来消化)的函数,但有时候想要搜索块并将结果返回给调用者。另一种模式是通过三个参数启用的 - 第二个参数是要搜索的信息(默认引用空字符串),第三个参数是返回数据(默认引用所需类型的空列表)。
这个范例在Visual Studio 2005和2008中有效,我们必须对其进行重构,以便构建并返回列表,而不是通过调用者和变异来使用g ++进行编译。
如果有办法将编译器开关设置为在MSVC中禁止这种行为或者在g ++中允许它,我会很高兴知道; MSVC编译器的允许性/ g ++编译器的限制性增加了移植代码的复杂性。