Suppose I have a cross-platform Path
class like:
class Path {
public:
// ...
Path parent() const; // e.g., /foo/bar -> /foo
std::string const& as_utf8() const {
return path;
}
private:
std::string path;
};
The parent()
member function returns the parent path of this
path, so it (rightly) returns a newly constructed Path
object that represents it.
For a platform that represents paths at the OS-level as UTF-8 strings (e.g., Unix), it seems reasonable for as_utf8()
to return a reference directly to the internal representation path
since it's already UTF-8.
If I have code like:
std::string const &s = my_path.as_utf8(); // OK (as long as my_path exists)
// ...
Path const &parent = my_path.parent(); // OK (temporary lifetime extended)
Both these lines are fine because:
- Assuming
my_path
persists, thens
remains valid. - The lifetime of the temporary object returned by
parent()
is extended by theconst&
.
So far, so good. However, if I have code like:
std::string const &s = my_path.parent().as_utf8(); // WRONG
then this is wrong because the temporary object returned by parent()
does not have its lifetime extended because the const&
does not refer to the temporary but to a data member of it. At this point, if you try to use s
, you'll either get garbage or a core dump. If the code were instead:
std::string as_utf8() const { // Note: object and NOT const&
return path;
}
then the code would be correct. However, it would be inefficient to create a temporary every time this member function is called. The implication is also that no "getter" member functions should ever return references to their data members.
If the API is left as-is, then it would seem to place an undue burden on the caller to have to look at the return type of as_utf8()
to see whether it returns a const&
or not: if it does, then the caller must use an object and not a const&
; if it returns an object, then the caller may use a const&
.
So is there any way to solve this problem such that the API is both efficient in most cases yet prevents the user from obtaining dangling references from seemingly innocuous looking code?
By the way, this was compiled using g++ 5.3. It's possible that the lifetime of the temporary should be extended, but that the compiler has a bug.
temp().member
andtemp().get()
whereget()
happens to returnmember
. – Tuftedget
returns a reference to the member then you have the member as the reference is just an alias. – Hainesget
returns a reference to a member? All the compiler can see is thatget
returns a reference. No, temporary lifetime extension only happens when you get an NSDM, not call a member function. – Volaget
is not inline? – Tufted