C++ copy elision用于引用

pat*_*oft 7 c++ concurrency stl thread-safety

考虑到以下简化代码,Cache :: operator []的调用者保证接收映射值的副本?

#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;
}
Run Code Online (Sandbox Code Playgroud)

正如我所看到的那样,我的意图是并发性,并且在释放互斥锁之后,不能保证映射值的继续存在.

std::map<>::operator[]返回一个引用std::string&.我的理解是,复制结构会产生一个无名的临时版,然后可能会受到RVO的影响.

何时会发生复制省略,这是否会导致不同的线程返回相同的对象而不是自己的副本?如果是这样,怎么能避免这种情况?

实际代码涉及数据库查找填充缓存,其中映射键是表主键,映射值是从行字段构造的对象.

jen*_*nsa 3

你的代码没问题。当编译器意识到它可以优化临时对象并就地构造新对象时,就会发生复制省略。事实上,map::operator[] 返回对其值类型的引用是无关紧要的,该函数不返回引用。所以,

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

// case 2
std::string myFunction(int k)
{
    return m_map[k];
}
Run Code Online (Sandbox Code Playgroud)

都会返回副本。不同之处在于,在第一种情况下,编译器很可能会使用复制省略/RVO(即不调用复制构造函数),而在第二种情况下,它必须调用复制构造函数并进行复制。

如果您的编译器没有使用复制省略/RVO,则根据 C++11 标准,返回值是临时值(在第一种情况下),并且由于类 std::string 是可移动的,因此临时值将被移动。例如,

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

因此,事先判断是否会使用移动语义或是否会发生复制省略/RVO 并不总是显而易见的,这取决于您的编译器。如果你愿意的话,你可以强制移动语义,通过使用

std::move
Run Code Online (Sandbox Code Playgroud)

编辑:顺便说一句,您甚至不被允许返回对临时对象的引用。您不能获取右值(临时)的引用。