I understand that returning a reference to a function argument could invoke undefined behaviour as in the below example. The first 'MyType' created goes out of scope after the function call and is destroyed, leading to a dangling reference.
#include <iostream>
struct MyType {
std::string data;
inline ~MyType() {
data = "Destroyed!!";
}
};
const MyType& getref(const MyType& x) {
return x;
}
int main(int argc, char *argv[]) {
const MyType& test = getref(MyType {"test"});
std::cout << test.data << std::endl;
return 0;
}
My questions are:
No matter how hard I tried allocating other things on the stack before the print, I couldn't get it to print the wrong thing without making the destructor explicitly change the data. Why is this?
You're invoking undefined behaviour; anything could happen at this point!
Why isn't there a clang warning for this? There is a warning for returning a ref to a local variable.
Are there any valid (safe) use cases of returning a reference to an argument?
std::max
Suppose you want the max of two values:
auto foo = std::max(x, y); // foo is a copy of the result
You might not want a copy to be returned (for performance or semantic reasons). Luckily, std::max returns a reference, allowing for both use cases:
auto foo = std::max(x, y); // foo is a copy of the result
auto& bar = std::max(x, y); // bar is a reference to the result
An example of where it matters for semantics reasons is when used in conjunction with std::swap:
std::swap(x, std::max(y, z));
Imagine that std::max returned a copy of y. Rather than swapping with x with y, x would we swapped with a copy of y. In other words, y would remain unchanged.
Assignment
A common use case of this is the assignment operator. Take this for example:
#include <iostream>
class T {
public:
T(int _x) : x(_x) { }
T& operator=(const T& rhs) { x = rhs.x; return *this; }
int getX() const { return x; }
private:
int x = 0;
};
int main() {
T instanceA(42);
T instanceB(180);
std::cout << (instanceA = instanceB).getX() << std::endl;
}
You can think of the hidden this parameter as a non-null pointer (so close enough to a reference for the purposes of this question).
Defining a copy-assignment operator as such is generally considered idomatic. One of the reasons this is so is because the automatically generated copy-assignment operator has that signature:
N4618 12.8.2.2 Copy/move assignment operator [class.copy.assign]
The implicitly-declared copy assignment operator for a class will have the form
X& X::operator=(const X&)
Making this a warning would penalize canonical code! As for why one might want to do such a thing (aside from it being canonical), that's another topic...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With