引用的生命周期是否会被延长?

16

我想把一个引用传递给一个函数。这段代码没有按照我的期望工作:

struct A {
};

void foo(A& a) {
    // do something with a
}

int main(int, char**) {
    foo(A());
}

我遇到了编译错误

非const引用类型 A& 的初始化无效,源类型为A

但是当我将方法 A& ref() 加入到A中,并在把它传递给函数之前调用该方法时,似乎可以使用a。在调试时,foo()被调用后,A对象会被销毁:

struct A {
    A& ref() {
        return *this;
    }
};

void foo(A& a) {
    // do something with a
}

int main(int, char**) {
    foo(A().ref());
}

这段代码符合标准吗?调用ref()会自动延长对象的生命周期,直到foo()返回吗?


5
虽然这在技术上是可能的,但将函数参数更改为 const A&A && 会更加简单。 - Sam Varshavchik
2
你的第一个代码片段无法工作,因为你试图将一个rvalue引用传递给一个接受非const lvalue引用的函数,这是没有意义的。这是不允许的,以防止你在f()中对A做出错误的更改,因为在f()中更改A将没有任何效果。 - Klaus
1
在实际使用这段代码时,我想修改变量a并将其赋值给另一个对象。 - martinus
@martinus 先分配,然后修改副本怎么样? - eerorika
1
@SamVarshavchik 我不能使用 const&,也不能使用 &&,因为不幸的是我的项目中不允许使用 C++11。 - martinus
显示剩余5条评论
3个回答

23

你的代码是完全有效的。

在这一行中

foo(A().ref());

一个临时 A 实例的生命周期仅限于语句的结束处 (;)。

这就是为什么安全地将从 ref() 返回的 A& 传递给 foo (只要 foo 没有存储它)。

ref() 本身不会扩展任何生命周期,但通过返回左值引用来提供帮助。

那么 foo(A()); 的情况会发生什么?在这里,临时变量作为 rvalue 传递。在 C++ 中,rvalue 不会绑定到非 const 左值引用 (即使在 C++11 中,rvalue 引用也不会绑定到非 const 左值引用)。

根据Visual C++关于rvalue引用的博客文章

...C++ 不希望您意外修改临时对象,但在可修改的 rvalue 上直接调用非 const 成员函数是显式的,因此允许这样做...


1
但是如果这是允许且有效的,为什么 foo(A()); 不被允许呢? - martinus
3
这是因为一个 rvalue 无法绑定到一个非 const 的引用上。 - rustyx
1
你应该明确提到,如果 foo(A()) 能够工作,那么它将会在语句的末尾保留 A();这里生命周期不是问题。 - Yakk - Adam Nevraumont
@martinus:你不能直接绑定一个非const的,但如果你可以绕过这个类型限制,那么引用扩展规则实际上仍然适用。 它们以更广泛的方式定义。 - Lightness Races in Orbit

7

A()创建了一个类型为A的临时对象。该对象存在于创建它的完整表达式结束之前。你原始代码中的问题不是这个临时对象的生命周期;而是函数将其参数作为非const引用,并且你不能将临时对象作为非const引用传递。最简单的更改是让foo通过const引用来接受它的参数,如果这符合函数的要求:

void foo(const A&);
int main() {
    foo(A());
}

我无法传递一个常量引用,在我使用它的代码中,我必须修改a之后再将其分配给另一个对象。 - martinus
1
不允许将临时对象作为非const左值引用传递,但可以将其作为非const右值引用传递(隐式创建临时变量)。 - Klaus

5
这个问题有几个方面需要解决:
首先,您不能将类型为A的临时值(prvalue)传递给接受A&的函数,因为非const左值引用无法绑定到右值。这是一种语言限制。如果您想能够传递临时值,您需要采用A&&A const&类型的参数,后者因为临时值可以绑定到const左值引用。
其次,您的程序中没有进行生命周期延长。根据[class.temp]:
"在三种情况下,临时对象的销毁时间不同于完整表达式的结束时间。第一种情况是当调用默认构造函数来初始化没有相应初始化器的数组元素时(8.6)。第二种情况是当调用复制构造函数来复制整个数组时复制一个数组元素(5.1.5,12.8)。[...]第三种情况是当引用绑定到临时对象时。"
这些情况都不适用于您的代码。我们从未将引用绑定到临时值。 ref()*this绑定到A&,但*this不是临时值,然后将该结果引用简单地传递到foo()中。
请考虑您程序的这个变体:
#include <iostream>

struct A {
    A& ref() { return *this; }
    ~A() { std::cout << "~A()\n"; }
};

int main() {
    auto& foo = A().ref();
    std::cout << "----\n";
}

这段代码实现了打印功能。

~A()
----

说明没有寿命延长。


如果我们不将ref()的结果绑定到引用上,而是将其绑定到成员上:

#include <iostream>

struct A {
    A& ref() { return *this; }
    int x;

    ~A() { std::cout << "~A()\n"; }
};

int main() {
    auto&& foo = A().x;
    std::cout << "----\n";
}

那么,实际上我们正在将一个临时对象绑定到一个引用上,并且第三个上下文应用 - 引用所绑定的子对象的完整对象的生存期将持续到引用的生存期。因此,这段代码会打印出:
----
~A()

*this并非临时对象」确实如此。但你说得对,它的生命周期并不需要延长。 - Lightness Races in Orbit
@LightnessRacesinOrbit *this 虽然总是一个左值。 - Barry

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