Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to return *this as a reference?

Tags:

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; } 
like image 993
anton_rh Avatar asked Feb 27 '16 11:02

anton_rh


People also ask

Should I return a reference?

Summary: it's okay to return a reference if the lifetime of the object won't end after the call.

What does return by reference mean?

It means you return by reference, which is, at least in this case, probably not desired. It basically means the returned value is an alias to whatever you returned from the function. Unless it's a persistent object it's illegal.

Why should we return by reference?

Functions can be declared to return a reference type. There are two reasons to make such a declaration: The information being returned is a large enough object that returning a reference is more efficient than returning a copy. The type of the function must be an l-value.

What is return * this in C++?

this means pointer to the object, so *this is an object. So you are returning an object ie, *this returns a reference to the object.


2 Answers

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; 
like image 127
JVApen Avatar answered Nov 08 '22 04:11

JVApen


// 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; 
like image 39
Christian Hackl Avatar answered Nov 08 '22 05:11

Christian Hackl