将新构建的对象以引用方式传递给函数是合法的吗?

17

具体来说,以下C++代码是否合法?

class A{};
void foo(A*); void bar(const A&);
int main(void) { foo(&A()); // 1 bar(A()); // 2 }

这段代码似乎可以正常工作,但这并不意味着它一定是合法的。 它合法吗?

编辑 - 将A&更改为const A&

9个回答

16

1: 取临时变量的地址是不被允许的。Visual C++ 作为一种语言扩展支持此操作(语言扩展默认开启)。

2: 这是完全合法的。


2
禁止获取rvalue的地址,而不是临时变量。这就是为什么这是被禁止的原因;A()返回一个rvalue。 - underscore_d
2
@underscore_d 对的 - 我使用了非正式术语“temporary”,更具体地说是一个未命名的临时变量,正如您所说,它是一个 rvalue。如果我们要挑剔的话,“A()”并不会返回一个 rvalue(构造函数没有返回值),它是一个 prvalue 表达式。 - James Hopkin
好的发现 - 太容易本能地将构造函数视为函数调用了。 - underscore_d

10

不可以将非const引用传递给临时对象,这与标准不符。您可以使用const引用:

class A{};

void bar(const A&);

int main(void)
{
    bar(A());  // 2
}

因此,尽管某些编译器可以接受它,并且只要在分号之后不使用内存,它就可以工作,但符合标准的编译器将不予接受。


6

foo在符合完全标准的C++中是不允许的,而bar则可以。尽管foo可能会编译出警告,而bar可能也会或者不会编译出警告。

A()会创建一个临时对象,除非将其绑定到引用(如在bar中的情况),或用于初始化命名对象,否则该对象将在创建它的完整表达式结束时被销毁。用于持有引用初始化器的临时对象将持续到其引用的作用域结束。对于bar的情况,那就是函数调用,所以你可以在bar内部完全安全地使用A。禁止将临时对象(即rvalue)绑定到非const引用。同样禁止获取rvalue的地址(将其作为参数传递以初始化fooA)。


需要进行一些重构,以清晰地表明引用临时变量的作用范围增强能力。 - Martin York
这是最佳答案,因为它正确地得出了禁止的内容是取rvalue的地址,而不是只针对临时值。 - underscore_d

3

简短的回答是可以。

如果对象被作为const引用参数传递给函数 - 正如你修改的bar(const A&)方法,那么这是完全合法的。函数可以操作该对象,但该对象将在函数调用后被销毁(可以获取临时地址,但不应在函数调用后存储和使用 - 请参见下面的原因)。

foo(A*)也是合法的,因为临时对象在完整表达式结束时被销毁。然而,大多数编译器会发出有关获取临时地址的警告。

原始版本的bar(A&)不应该编译,从临时对象初始化非const引用是违反标准的。

C++标准第12.2章

3 [...] 临时对象在评估包含它们创建点的(词法上)完整表达式(1.9)的最后一步中被销毁。[...]

4 有两种情况下,临时对象的销毁时间与完整表达式的结束点不同。第一种情况是当表达式出现为定义对象的声明符的初始值时。在这种情况下,保存表达式结果的临时对象必须持续到对象的初始化完成为止。[...]

5 第二种情况是当引用被绑定到一个临时对象时。除非下面指定,引用所绑定的临时对象或作为该临时对象完整对象的子对象的临时对象将持续到引用的生命周期结束。在构造函数的ctorinitializer(12.6.2)中绑定到引用成员的临时对象将持续到构造函数退出。在函数调用(5.2.2)中绑定到引用参数的临时对象将持续到包含该调用的完整表达式完成。绑定到函数返回语句(6.6.3)中返回值的临时对象将持续到函数退出。

完整表达式是不是其他表达式的子表达式的表达式。


不,foo( &A() )是不合法的,因为A()是一个右值,你不能取一个右值的地址。https://dev59.com/VU7Sa4cB1Zd3GeqP2EMt#2985578 - underscore_d

1

那些A对象只存在到执行到分号为止。所以,调用是安全的,但不要尝试保存指针并在稍后使用它。此外,编译器可能需要bar采用const引用。


如果foo(&A())能够编译通过,它可能是“安全”的,但这并不是有效的C++。 - underscore_d

0

看起来它应该可以工作,但是在使用 g++ 和 -Wall 选项编译时无法通过,这是我得到的结果:

michael@hardy-lenovo:~/Desktop$ g++ -Wall a.cpp
a.cpp: In function ‘int main()’:michael@hardy-lenovo:~/Desktop$ g++ -Wall a.cpp
a.cpp: In function ‘int main()’:
a.cpp:8: warning: taking address of temporary
a.cpp:9: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
a.cpp:4: error: in passing argument 1 of ‘void bar(A&)’
michael@hardy-lenovo:~/Desktop$ 

看起来你需要使用一个常量引用。


但是,MSVC和g++都对(1)发出警告的事实表明它可能不是100%合法的。您有引用C ++标准的章节吗? - Adam Rosenfield

-1

这是合法的。有时我们使用它来提供一个默认值,但可能会忽略它。

int dosomething(error_code& _e = ignore_errorcode()) {
    //do something
}

在上述情况下,如果未向函数传递error_code,它将构造一个空的错误代码对象。

我非常确定你的ignore_errorcode()返回一个error_code对象,其生命周期比通过默认构造函数创建的对象更长... - Pieter

-1

//2 需要一个常量引用

//1 我认为它是合法但无用的


不,foo( &A() )是不合法的,因为A()是一个右值,你不能取一个右值的地址。https://dev59.com/VU7Sa4cB1Zd3GeqP2EMt#2985578 - underscore_d

-2

完全合法。

该对象将在函数调用期间存在于堆栈上,就像任何其他本地变量一样。


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