获取临时对象的地址

34

§5.3.1 一元操作符,第三节

一元&操作符的结果是其操作数的指针。操作数应该是一个左值或者限定符ID。

"shall be"在这里的确切含义是什么?这是否意味着获取临时变量的地址是错误的?我只是想知道,因为g++只给了我一个警告,而comeau拒绝编译以下程序:

#include <string>

int main()
{
    &std::string("test");
}

g++ 警告: 取临时变量的地址

Comeau 错误: 表达式必须是左值或函数指针

请问有人有Microsoft编译器或其他编译器可以测试一下这个程序吗?


很有趣。我没有使用Comeau的经验,但我想知道它是否因为模板而感到困惑?如果你尝试一些创建的数组,比如&array[0],会发生什么? - Casey
&Casey &array[0] 是完全合法的 - array[0] 是一个左值。 - anon
7个回答

37
标准语言中的“shall”一词意味着严格要求。因此,是的,您的代码格式不正确(是错误的),因为它试图将地址运算符应用于非左值。但是,问题并不在于尝试获取临时对象的地址。问题在于再次获取非左值的地址。临时对象可以是左值或非左值,这取决于产生该临时对象或提供对该临时对象访问的表达式。在您的情况下,您有一个对非引用类型的函数样式转换std::string("test"),根据定义会产生一个非左值。因此出现了错误。如果您想要获取临时对象的地址,您可以通过以下方式解决限制:
const std::string &r = std::string("test");
&r; // this expression produces address of a temporary

使用这种方法产生的指针在临时对象存在期间保持有效。还有其他合法的方法可以获得临时对象的地址,只是你的特定方法恰好是非法的。


8
+1 是针对 rvalue 和 temporary 的区别。人们常常将它们视为相等,但实际上它们并不相同。一个常见例子是异常对象,它们是 lvalue,可以绑定到 catch 子句中的 T& - Johannes Schaub - litb
1
事实上,如果我没有弄错的话,标准本身似乎误用了“临时”的术语。它说一个绑定到函数返回值的临时对象应该在函数返回后被销毁。然而,这意味着struct A { A &ref() { i++; return *this; } int i; }; int main() { A().ref().ref(); }会调用未定义的行为,因为在第一个ref()之后,对象就不存在了。它忽略了只有那些尚未绑定到引用(包括隐式对象参数)的对象才被认为是这样的对象。 - Johannes Schaub - litb
@JohannesSchaub-litb:“它说一个绑定到函数返回值的临时对象应该在函数返回后被销毁。” 看起来临时对象和右值被当作同义词使用。 - curiousguy
@curiousguy:异常对象始终是临时的,也就是说,无论何时抛出异常,实际上都会抛出该“something”的临时副本。这对我来说非常有意义,因为异常对象的生命周期在幕后被隐式地管理。编译器必须拥有它才能具有必要的自由度来管理它。所以,它会制作自己的副本。当然,您可以抛出指向对象而不是实际对象的指针,在这种情况下,您将保留对象的所有权。但我认为这不是一个好的做法。 - AnT stands with Russia
@AndreyT "_一个被抛出的“something”的临时副本_”。是的,throw会创建一个临时副本。但是@JohannesSchaub-litb描述的是在catch子句中的引用绑定:catch声明本身不会“管理异常对象的生命周期”(但是在catch块之外的goto会这样做)。catch声明不处理临时值(也称为“rvalue”),而是处理lvalue:大多数情况下,异常对象由(非共享)shared_ptr处理,该shared_ptr是catch块的临时变量,而不是shared_ptr本身。(当然,它不仅仅是这些。) - curiousguy
显示剩余4条评论

7
在C++标准中使用“shall”一词时,它意味着“必须遵守,否则就是错误的”。如果一个实现不遵守这个规定,那么它就是有缺陷的。

1
实际上,这仅适用于那些形式为“实现应该执行X”的语句。对于形式为“输入代码应该执行X”的语句,它意味着“必须符合诊断的规定”。 - MSalters
2
@MSalters必须在不得不的情况下发布诊断,否则就会被处死。 - anon
3
顺便说一下,我一直认为在你自己回答问题的同时,对其他答案进行投票downvote是可疑的做法。 - anon
8
我不明白为什么。如果您认为答案不正确,无论您是否自己编写了答案,都应该将其投票降级。我认为我们足够成熟,可以处理这个问题,不是吗?或者人们真的那么重视声望竞赛吗? - jalf
1
看,SO的目的是作为答案库。通常会有多个答案,那么作为后来的读者,如何选择明智的答案呢?投票旨在突出好的答案。我回答得非常明确,因为我发现得分最高的答案是不正确的,出于同样的原因,我对它进行了负投票。 - MSalters
显示剩余6条评论

3

在启用了已弃用的 /Ze(启用扩展)选项的 MSVC 中,是允许这样做的。在先前版本的 MSVC 中也是允许的。它将生成一个带有所有警告启用的诊断:

警告 C4238:使用了非标准扩展:将类右值用作左值。

除非使用 /Za 选项(强制 ANSI 兼容性),否则会出现以下错误:

错误 C2102:'&' 需要 l 值


1

&std::string("test");是在请求函数调用的返回值的地址(我们将忽略该函数是构造函数这个无关紧要的事实)。在将其分配给某个变量之前,它没有地址。因此会出现错误。


1
当然,如果函数调用返回值是左值,你可以获取它们的地址。 - anon
但是如果你正在调用一个函数,那么这不就是一个右值吗? - TMN
2
@TMN 不是这样的,在C++中你可以返回一个引用,它是一个左值(lvalue)。这就是像std::vector的operator[]如何工作的。 - anon
4
@James Curran:不正确。这不是一个函数调用,而是一种函数式转换。如果它是一个lvalue,你可以对一个转换的结果(以及函数调用的结果)取地址。例如,引用始终是lvalue - AnT stands with Russia

1
C++标准实际上是符合C++实现的需求。在某些地方,它被写成区分符合实现必须接受的代码和符合实现必须给出诊断的代码。
所以,在这种特定情况下,符合规范的编译器必须在取一个右值的地址时给出诊断。两个编译器都这样做了,所以它们在这方面是符合规范的。
标准不禁止在某些输入导致诊断的情况下生成可执行文件,即警告是有效的诊断。

0

我不是标准专家,但这肯定听起来像一个错误。g++ 经常只对真正的错误发出警告。


-3

用户定义的转换

struct String {
    std::string str;

    operator std::string*() {
        return &str;
    }
};

std::string *my_str = String{"abc"};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接