Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ copy elision for references

Considering the following simplified code is the caller of Cache::operator[] guaranteed to receive a copy of the mapped value?

#include <string>
#include <map>
#include <mutex>
#include <iostream>

class Cache {
    public:
        std::string operator[] (int k) {
            std::lock_guard<std::mutex> lock(m_mutex);

            if (! m_map.count(k)) m_map[k] = "Hello world";
            return m_map[k];
        }

    private:
        std::mutex m_mutex;
        std::map<int, std::string> m_map;
};

int main (int argc, char *argv[]) {
    Cache c;
    auto v = c[42];
    std::cout << v << std::endl;
    return 0;
}

As seen my intention is concurrency and after release of the mutex the continued existence of the mapped value is not guaranteed.

std::map<>::operator[] returns a reference std::string&. My understanding is that copy construction produces a nameless temporary which may then be subject to RVO.

When will copy elision occur and could this result in different threads being returned the same object rather than their own copies? If so how can this be avoided?

The actual code is involves a database lookup filling the cache with the map key being the table primary key and the mapped value an object constructed from the row fields.

like image 795
patchsoft Avatar asked Jul 13 '15 08:07

patchsoft


1 Answers

The code you have is fine. Copy elision occurs when the compiler realizes that it can optimize away temporary objects and instead construct the new object in place instead. The fact that map::operator[] returns a reference to its value type is irrelevant, the function is not returning a reference. Therefore,

// case 1
std::string myFunction()
{
    return std::string("Hello");
}

// case 2
std::string myFunction(int k)
{
    return m_map[k];
}

will both return copies. The difference is that in the first case your compiler will most likely use copy elision/RVO (i.e. copy constructor is not called), whereas in the second case it must call the copy constructor and make a copy.

If your compiler did NOT utilize copy elision/RVO, than by the C++11 standard the returned value is a temporary (in the first case), and since the class std::string is movable, the temporary would be moved. For example,

std::string newStr = myFunction(); // RHS returns an r-value => move-semantics is used

It is therefore not always obvious beforehand to say if move-semantics will be used or if copy elision/RVO will occur, it depends on your compiler. You can force move-semantics if you want though, by using

std::move

Edit: Btw, you wouldn't even be allowed to return a reference to a temporary. You can't take the reference of an r-value (temporary).

like image 165
jensa Avatar answered Sep 24 '22 12:09

jensa