引用类型转换运算符:自找麻烦?
当我使用 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; }
};
那么一切都很好! 我的问题是这是否安全。 当 A()
被构造为临时值时,this
到底指向什么?
这一事实给了我一些信心。
void foo(const A&) {}
可以根据 g++
和我使用过的所有其他编译器接受临时值, const 关键字始终可以被丢弃,因此如果 const A&
参数和 A&
参数之间存在任何实际语义差异,我会感到惊讶。代码>参数。 所以我想这是问我的问题的另一种方式:为什么编译器认为对临时值的 const
引用是安全的,而非 const
引用则不然?
When I compile the following code using g++
class A {};
void foo(A&) {}
int main()
{
foo(A());
return 0;
}
I get the following error messages:
> 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&)’
After some reflection, these errors make plenty of sense to me. A()
is just a temporary value, not an assignable location on the stack, so it wouldn't seem to have an address. If it doesn't have an address, then I can't hold a reference to it. Okay, fine.
But wait! If I add the following conversion operator to the class A
class A
{
public:
operator A&() { return *this; }
};
then all is well! My question is whether this even remotely safe. What exactly does this
point to when A()
is constructed as a temporary value?
I am given some confidence by the fact that
void foo(const A&) {}
can accept temporary values according to g++
and all other compilers I've used. The const
keyword can always be cast away, so it would surprise me if there were any actual semantic differences between a const A&
parameter and an A&
parameter. So I guess that's another way of asking my question: why is a const
reference to a temporary value considered safe by the compiler whereas a non-const
reference is not?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这并不是说地址不能被获取(编译器总是可以命令将其推入堆栈,这是通过 ref-to-const 实现的),这是程序员意图的问题。 对于采用 A& 的接口,它表示“我将修改此参数中的内容,以便您可以在函数调用后读取”。 如果您将其传递为临时值,那么它“修改”的内容在该函数之后不存在。 这(可能)是一个编程错误,因此是不允许的。 例如,考虑一下:
这不会编译,但如果临时变量可以绑定到非常量引用,它会编译但会产生令人惊讶的结果。 在 plus_one(f) 中,f 将隐式转换为临时 int,plus_one 将获取 temp 并递增它,使底层浮点 f 保持不变。 当 plus_one 返回时,它不会有任何效果。 几乎可以肯定这不是程序员的意图。
规则有时确实会混乱。 一个常见示例(此处进行了描述) ,正在尝试打开一个文件,打印一些内容,然后关闭它。 您希望能够执行以下操作:
但您不能,因为运算符<< 采用非 const 引用。 您的选择是将其分成两行,或者调用返回引用非 const 的方法:
It isn't that an address can't be taken (the compiler could always order it shoved on the stack, which it does with ref-to-const), it's a question of programmers intent. With an interface that takes a A&, it is saying "I will modify what is in this parameter so you can read after the function call". If you pass it a temporary, then the thing it "modified" doesn't exist after the function. This is (probably) a programming error, so it is disallowed. For instance, consider:
This doesn't compile, but if temporaries could bind to a ref-to-non-const, it would compile but have surprising results. In plus_one(f), f would be implicitly converted to an temporary int, plus_one would take the temp and increment it, leaving the underlying float f untouched. When plus_one returned, it would have had no effect. This is almost certainly not what the programmer intended.
The rule does occasionally mess up. A common example (described here), is trying to open a file, print something, and close it. You'd want to be able to do:
But you can't because operator<< takes a ref-to-non-const. Your options are break it into two lines, or call a method returning a ref-to-non-const:
当您将右值分配给 const 引用时,可以保证在引用被销毁之前临时不会被销毁。 当您分配给非常量引用时,不会做出此类保证。
你不能随心所欲地安全地抛弃常量并期望事情能够正常进行。 const 和非常量引用有不同的语义。
When you assign an r-value to a const reference, you are guaranteed that the temporary won't be destroyed until the reference is destroyed. When you assign to a non-const reference, no such guarantee is made.
You can't safely cast away const-ness willy nilly and expect things to work. There are different semantics on const and non-const references.
有些人可能会遇到一个问题:MSVC 编译器(Visual Studio 编译器,已使用 Visual Studio 2008 进行验证)将毫无问题地编译此代码。 我们一直在一个项目中使用这种范例,该函数通常采用一个参数(要消化的一大块数据),但有时想要搜索该块并将结果返回给调用者。 另一种模式是通过采用三个参数来启用的——第二个参数是要搜索的信息(默认引用空字符串),第三个参数用于返回数据(默认引用所需类型的空列表)。
这种范例在 Visual Studio 2005 和 2008 中有效,我们必须重构它,以便构建并返回列表,而不是使用 g++ 进行编译的“由调用者拥有并变异”。
如果有一种方法可以将编译器开关设置为在 MSVC 中禁止这种行为或在 g++ 中允许这种行为,我会很高兴知道; MSVC 编译器的宽松性/g++ 编译器的限制性增加了移植代码的复杂性。
A gotcha that some people may run into: the MSVC compiler (Visual Studio compiler, verified with Visual Studio 2008) will compile this code with no problems. We had been using this paradigm in a project for functions that usually took one argument (a chunk of data to digest), but sometimes wanted to search the chunk and yield results back to the caller. The other mode was enabled by taking three arguments---the second argument was the information to search on (default reference to empty string), and the third argument was for the return data (default reference to empty list of the desired type).
This paradigm worked in Visual Studio 2005 and 2008, and we had to refactor it so that the list was built and returned instead of owned-by-caller-and-mutated to compile with g++.
If there is a way to set the compiler switches to either disallow this sort of behavior in MSVC or allow it in g++, I would be excited to know; the permissiveness of the MSVC compiler / restrictiveness of the g++ compiler adds complications to porting code.