C ++ copy elision用于引用

时间:2015-07-13 08:35:17

标签: 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;
}

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

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

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

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

2 个答案:

答案 0 :(得分:3)

你拥有的代码很好。当编译器意识到它可以优化掉临时对象而不是在适当的位置构造新对象时,就会发生复制省略。 map :: operator []返回对其值类型的引用这一事实无关紧要,该函数未返回引用。因此,

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

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

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

如果您的编译器没有使用copy elision / RVO,而不是C ++ 11标准,则返回的值是临时的(在第一种情况下),并且因为类std :: string是可移动的,所以临时将是移动。例如,

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

因此,预先说明是否使用move-semantics或者是否会出现copy elision / RVO并不总是很明显,这取决于你的编译器。如果您愿意,可以使用

强制移动语义
std::move

编辑:顺便说一句,你甚至不会被允许返回对临时的引用。你不能参考r值(临时)。

答案 1 :(得分:1)

复制elision只是一种编译器优化,它是为了避免在处理临时对象时出现不必要的副本而实现的。在您的情况下,您不是返回临时值,而是返回成员变量中的值。由于您按值返回,因此将复制它。没有办法解决它。