Reputation: 387
I have a function returning a reference to an instance of my class "record".
record& get_record(int key) {
return lookup(key);
}
That is effective it returns a reference and not by value. Now I modify it a bit.
record& get_record(int key) {
if (valid(key))
return lookup(key);
else {
record x;
x.valid=false;
return x; //Here I really want to return a temporary variable
// and not a reference to a local variable.
}
}
Is it correct that returning a reference to a local variable is not a good idea? and how do I return x in such a way that it becomes a temporary variable?
Upvotes: 4
Views: 12751
Reputation: 40160
This is worse than a bad idea, it is undefined behavior and result in most of the cases to a crash. This is bad (TM).
What you could do is changing the return type of get_record
so it returns a smart pointer. If key
is valid, it returns an observer pointer to it. Otherwise, it returns an owning smart pointer to a freshly created object:
#include <memory>
#include <iostream>
struct record { int n; } some_record{42};
std::shared_ptr<record> get_record(bool b)
{
if (b == true) {
return std::shared_ptr<record>{&some_record, [](record*){}}; // see explanation ^1
}
return std::shared_ptr<record>{new record{0}};
}
int main()
{
std::cout << get_record(true)->n << "\n"; // this is some_record
std::cout << get_record(false)->n << "\n"; // this is a temporary
}
1) About [](record*){}
: this no-op lambda given as the second argument to std::shared_ptr::shared_ptr()
is invoked when the smart pointer is destroyed. It replaces the default deleter of std::shared_ptr
whose behavior is to call delete
on the owned pointer.
About why your design is flawed. In fact, making get_record
return a reference makes it not consistent. What you want is:
key
is valid return a reference to an existing/permanant object, andThose two are mutually exclusive, and your function doesn't make sense: what does get_record
return semantically?
Upvotes: 7
Reputation: 73713
Returning a reference in itself doesn't produce undefined behaviour, but if you attempt to modify it, then you will.
Accessing an object outside of its lifetime is undefined behavior.
int* foo(void) {
int a = 17; // a has automatic storage duration
return &a;
} // lifetime of a ends
int main(void) {
int* p = foo(); // p points to an object past lifetime ("dangling pointer")
int n = *p; // undefined behavior
}
http://en.cppreference.com/w/c/language/lifetime
If you have access to C++17, you could implement it using std::optional. Note the use of std::reference_wrapper, because use of a reference in std::optional
makes your program ill-formed.
std::optional<std::reference_wrapper<record>> get_record(int key) {
if (valid(key))
return std::optional<std::reference_wrapper<record>>(lookup(key));
else
return std::nullopt;
}
Without C++17, you could just return a pointer to your record:
record* get_record(int key) {
if (valid(key))
return &lookup(key);
else
return nullptr;
}
Or if you prefer, you can keep the reference return type, and throw an exception to indicate a missing record. Though this is my least preferred approach as it makes it easy to call get_record
without wrapping in a try / catch
.
record& get_record(int key) {
if (valid(key))
return &lookup(key);
else
throw std::out_of_range("Invalid record key!");
}
Upvotes: 0
Reputation: 156
If you are allowed to modify the get_record function you can change the return type to pointer to record instead of reference to record.
record* get_record( int key )
{
if( valid( key ) ) return &lookup( key );
else return nullptr;
}
However, this approach needs two guarantees:
Upvotes: 1
Reputation: 3068
As others already detail why returning a reference to a local is bad, ill just provide an alternative: Exceptions. Though you could write a custom exception, arguably an std::out_of_range
exception would seem in place (as the key lies outside of the range of valid keys, which is exactly what std::map::at
does).
Have a look at: How to throw a C++ exception
record& get_record(int key)
{
if (valid(key))
{
return lookup(key);
} else {
throw std::out_of_range("The provided key is invalid");
}
}
You will now, obviously, have to catch the exception in the code calling get_record
, otherwise your program will still terminate.
Upvotes: 0
Reputation: 173044
Is it correct that returning a reference to a local variable is not a good idea?
Yes. The local object will be destroyed when get out of the function so the returned reference is always dangled.
You might make x
a static
variable.
record& get_record(int key) {
if (valid(key))
return lookup(key);
else {
static record x;
x.valid=false;
return x;
}
}
Note that the returned reference will always refer to the same object, i.e. x
.
Upvotes: 7