参考类型转换操作符:要求麻烦?

时间:2009-06-24 20:57:10

标签: c++ operators reference type-conversion operator-keyword

使用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引用不是?

3 个答案:

答案 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 ++编译器的限制性增加了移植代码的复杂性。