return *this 作为参考是否安全?

Is it safe to return *this as a reference?

返回对该对象的引用经常在 assignment operator overloading. It is also used as a base for named parameters idiom 中使用,它允许通过对 setter 方法的调用链来初始化对象:Params().SetX(1).SetY(1) 每个 returns参考 *this.

但是 return 引用 *this 是否正确?如果我们为临时对象调用方法 returning 对此的引用会怎样:

#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;
}

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

12.2/3: Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created.

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

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

// Are the following constructions are correct:
std::cout << MakeObj(1).Me().member << std::endl;
std::cout << Obj(2).Me().member << std::endl;

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

正如cppreference.com所说:

(...) all temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created (...).

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

// 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;

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

Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. This is true even if that evaluation ends in throwing an exception (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;

这是合乎逻辑的,因为您正在 return引用临时文件。大多数编译器 warn/error 对此进行了处理,但如果您的代码变得复杂,则需要注意这一点。

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

Obj &Me() & { return *this; }
Obj &Me() && = delete;