Is it safe to return *this as a reference?
Asked Answered
Z

3

23

Returning reference to this object is often used in assignment operator overloading. It is also used as a base for named parameters idiom which allows to initialize object by chain of calls to setter methods: Params().SetX(1).SetY(1) each of which returns reference to *this.

But is it correct to return reference to *this. What if we call the method returning reference to this for a temporary object:

#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;
}
Zeiger answered 27/2, 2016 at 11:55 Comment(2)
Both of those usages are safe. The problem would only arise if the calling code tried to hold the reference for longer than the current statement, e.g. auto& ref = MakeObj(5).Me();. Yes, you can create an lvalue that designates a temporary object this way (which is one reason I often correct people who use the terms "temporary object" and "rvalue" interchangeably)Stonybroke
I noticed that standard library also uses returning *this - cplusplus.com/reference/vector/vector/operator= : "Return value: *this". It would be strange if standard library used incorrect constructions. :)Zeiger
C
12

Yes, it is safe to return *this. The easy case is when this is not a temporary, though even when it is, this should be possible:

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).

In other words, until you reach a semi-colon everything should be fine (in theory).

So following code should work:

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

While this should not work:

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

This is logical, as you are returning a reference to a temporary. Most compilers warn/error on this, though if you code gets to complex, this is something to watch out for.

Personally, I would prevent calling these methods on a temp object to enforce API users to think about the lifetime of the object. Which can be done by overloading your method: (If your compiler supports it already)

Obj &Me() & { return *this; }
Obj &Me() && = delete;
Cristiano answered 27/2, 2016 at 12:55 Comment(2)
Thank you for pointing out to this kind of overload. How is it named? Which version of C++ standard introduced this?Zeiger
I think it is C++11, though at least MSVC2013 doesn't support is. From what I can read in open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2439.htm I would call it function overloading on ref-qualifier of the this-ptr, don't know the official name.Cristiano
M
6
// Are the following constructions are correct:
std::cout << MakeObj(1).Me().member << std::endl;
std::cout << Obj(2).Me().member << std::endl;

Yes, because in each line the lifetime of all temporary objects is extended to take the full expression into account.

As cppreference.com says:

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

If you try to split up the full expression, then you will (hopefully) get a compiler error or warning:

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

In other cases, the compiler may not be smart enough to see the problem, create your executable without giving any diagnostic message, and ultimately building undefined behaviour into your program:

// undefined behaviour:
Obj &ref = MakeObj(1).Me();
std::cout << ref.member << std::endl;
Mabe answered 27/2, 2016 at 12:11 Comment(1)
@M.M: Thank you, I added the example.Mabe
I
5

Yes, this is safe. The temporary object's life will be until the end of the statement (more precisely the evaluation of the full expression in which it is created). This is guaranteed by the standard:

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.

Temporary lifetime may even be extended under some conditions if bound to a reference. But don't expect miracles here. Trying to keep the reference beyond the statement (f.ex by taking the address or assigning a reference) could quickly lead to UB (demo).

If you'd use this kind of construct on const objects you'd also have some troubles as you'd try to return a non const ref (but this isn't relevant in your examples for assignment and setters).

Internuncial answered 27/2, 2016 at 12:6 Comment(2)
Adding const Obj& Me() const { return *this; } as an overload adds support for const objects.Duress
@Duress yes, exacly ! I just wanted to draw attention that in general constness or non-constness of operations should be considered carefully especially when returning *this for a reference. Of course for assignment operator and setters this shall not be an issue ;-)Internuncial

© 2022 - 2024 — McMap. All rights reserved.