15

I have a std::unordered_map<int, std::string> and a function GetString(int key) that takes an int key and returns a string value from this map.

When the key isn't found in the map, I have to return an empty string.

#include <iostream>
#include <string>
#include <unordered_map>

std::unordered_map<int, std::string> map
{
    { 5, "somelongstring" }
};

const std::string& GetString(int key)
{
    auto iterator = map.find(key);
    if (iterator == map.end())
    {
        return "";
    }

    return iterator->second;
}

int main()
{
    std::cout << GetString(1) << std::endl;
}

The problem is the compiler gives me this warning

warning C4172: returning address of local variable or temporary

(with MS Visual Studio 2013) or

warning: returning reference to temporary [-Wreturn-local-addr]

(with g++ 4.9.2)

One way to get out of this I found was to declare a static const std::string at the top and return that instead of the empty string literal

static const std::string Empty = "";

const std::string& GetString(int key)
{
    auto iterator = map.find(key);
    if (iterator == map.end())
    {
        return Empty;
    }

    return iterator->second;
}

But it didn't seem very clean to define an empty string literal. Is there a neat way to do this?

Update: My map is initialized once during startup and then read simultaneously from multiple threads (using GetString). Using a function static empty string wouldn't work because function static variables aren't initialized in a thread safe manner under visual studio's compiler.

tcb
  • 4,408
  • 5
  • 34
  • 51
  • 1
    I don't think the `std::unordered_map` has anything to do with your question, really. – einpoklum May 14 '15 at 13:58
  • 16
    The const string could be a static in the function instead of global. – Borgleader May 14 '15 at 13:58
  • I don't think there is a better solution. You need a `string` instance which is still valid when you return the reference, or you return a `string` instance yourself (not a reference), but that might be inefficient. – Daniel Frey May 14 '15 at 14:01
  • @DanielFrey: But why should the literal not be valid always? – einpoklum May 14 '15 at 14:04
  • static string in the function wouldn't have a thread safe initialization under visual studio's compiler – tcb May 14 '15 at 14:05
  • 1
    @einpoklum The literal is of type `const char[1]`, to return a reference to a `std::string`, a temporary is created and you can't (legally) return a reference to a temporary. – Daniel Frey May 14 '15 at 14:05
  • 1
    @einpoklum the literal is *not* a `std::string`. It's a C string. To return it from this function, it must be converted to a `std::string`, creating a temporary in the process. A reference to that temporary is returned, just as it's going out of scope and being destroyed. – Paul Roub May 14 '15 at 14:06
  • Can you use Boost? If yes, you might consider returning a [StringRef](http://www.boost.org/doc/libs/release/libs/utility/doc/html/string_ref.html) or [Optional](http://www.boost.org/doc/libs/release/libs/optional/doc/html/) – Praetorian May 14 '15 at 14:24
  • @Praetorian Just be aware that even if `boost::optional` accepts a reference as parameter type, `std::experimental::optional` currently does *not* allow references. – Daniel Frey May 14 '15 at 14:30

2 Answers2

14

The warning message explicitely says what the problem is : you are returning the address of a local variable (""") that will be freed from stack after function returns. It would be fine to return a std::string, because you would construct a new string outside of the function local variables, but as you return a std::string& you do use the local variable.

But as you return a static value, just make it static :

const std::string& GetString(int key)
{
    static const string empty = "";
    auto iterator = map.find(key);
    if (iterator == map.end())
    {
        return empty;
    }

    return iterator->second;
}
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Afaik, function static variables are not intialized in a thread safe manner under visual studio's compiler, although c++ 11 standard requires this – tcb May 14 '15 at 14:12
  • @tcb You are right. With your edits, I now think that unless you can make sure that the function will be called at least once (for example just after initialization of the map), a *global* static const is the best way. – Serge Ballesta May 14 '15 at 15:36
  • C++11 "magic statics" are now supported by Visual Studio 2015 compiler too - https://blogs.msdn.microsoft.com/vcblog/2015/06/19/c111417-features-in-vs-2015-rtm/ – Yirkha Mar 20 '16 at 20:32
4

I would change the return type to either std::string (and so returning an empty string is ok) or to std::string * (returning nullptr for not found).

Otherwise the compiler is right: you can't return a reference to a local object about to be destoyed

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
marom
  • 5,064
  • 10
  • 14
  • returning std::string isn't an option given my map contains huge strings and I wouldn't like to make any copy. I am considering returning a pointer, but I would like to avoid pointers if possible. – tcb May 14 '15 at 14:19
  • If you return a pointer, than also consider to make that `const std::string*` as the OP also returned `const std::string&`. Also, it changes the interface for the caller, so I don't really like the idea. – Daniel Frey May 14 '15 at 14:19