23

返回对该对象的引用通常用于赋值运算符重载。它还用作命名参数习惯用法的基础,它允许通过调用 setter 方法链来初始化对象:Params().SetX(1).SetY(1)每个方法都返回对 *this 的引用。

但是返回对*this. 如果我们为临时对象调用返回对 this 的引用的方法会怎样:

#include <iostream>

class Obj
{
public:
    Obj(int n): member(n) {}
    Obj& Me() { return *this; }

    int member;
};

Obj MakeObj(int n)
{
    return Obj(n);
}

int main()
{
    // Are the following constructions are correct:
    std::cout << MakeObj(1).Me().member << std::endl;
    std::cout << Obj(2).Me().member << std::endl;
    Obj(3).Me() = Obj(4);

    return 0;
}
4

3 回答 3

12

是的,退回 *this 是安全的。最简单的情况是,这不是暂时的,但即使是,这也应该是可能的:

临时对象被销毁作为评估完整表达式(1.9)的最后一步,该完整表达式(在词法上)包含它们被创建的点。即使评估以抛出异常结束(C++03 §12.2/3)也是如此。

换句话说,在你到达分号之前,一切都应该没问题(理论上)。

所以下面的代码应该可以工作:

std::cout << MakeObj(1).Me().member << std::endl;

虽然这不应该工作:

const Obj &MakeMeObj(int n) { return Obj(n).Me(); }
std::cout << MakeMeObj(1).member << std::endl;

这是合乎逻辑的,因为您正在返回对临时的引用。大多数编译器都会对此发出警告/错误,但如果您的代码变得复杂,则需要注意这一点。

就个人而言,我会阻止在临时对象上调用这些方法来强制 API 用户考虑对象的生命周期。这可以通过重载您的方法来完成:(如果您的编译器已经支持它)

Obj &Me() & { return *this; }
Obj &Me() && = delete;
于 2016-02-27T12:55:58.007 回答
6
// Are the following constructions are correct:
std::cout << MakeObj(1).Me().member << std::endl;
std::cout << Obj(2).Me().member << std::endl;

是的,因为在每一行中,所有临时对象的生命周期都被延长以考虑完整的表达式

正如cppreference.com所说:

(...) 作为评估完整表达式的最后一步,所有临时对象都被销毁,该完整表达式(在词法上)包含创建它们的点 (...)。

如果您尝试拆分完整的表达式,那么您(希望)会收到编译器错误或警告:

// not allowed:
Obj& ref = MakeObj(1);
std::cout << ref.Me().member << std::endl;

在其他情况下,编译器可能不够聪明,无法发现问题,在不提供任何诊断消息的情况下创建可执行文件,并最终在程序中构建未定义的行为:

// undefined behaviour:
Obj &ref = MakeObj(1).Me();
std::cout << ref.member << std::endl;
于 2016-02-27T12:11:03.267 回答
5

是的,这是安全的。临时对象的生命周期将持续到语句结束(更准确地说,是对创建它的完整表达式的求值)。这是由标准保证的:

12.2/3:临时对象被销毁作为评估完整表达式的最后一步,该完整表达式(词法上)包含它们被创建的点。

如果绑定到引用,在某些条件下甚至可以延长临时生命周期。但不要指望这里有奇迹。试图将引用保留在语句之外(通过获取地址或分配引用来 f.ex)可能会很快导致 UB(演示)。

如果您在对象上使用这种构造,const您也会遇到一些麻烦,因为您会尝试返回非constref(但这与您的赋值和设置器示例无关)。

于 2016-02-27T12:06:21.823 回答