2015-07-13 98 views
7

考慮到以下簡化代碼是Cache :: operator []的調用者保證接收映射值的副本參考C++複製elision

#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影響。

何時將複製elision發生,並可能導致不同的線程返回相同的對象,而不是自己的副本?如果是的話,如何避免這種情況?

實際的代碼涉及填充緩存的數據庫查找,映射鍵是表主鍵,映射值是從行字段構造的對象。

+0

你的代碼沒問題。 – inf

+0

@inf謝謝。所以,如果我明白正確地更改'Cache :: operator [](int)'返回一個引用('std :: string&')打破所需的併發? – patchsoft

+1

是的,因爲那時用戶可以通過參考而不是副本直接訪問。 – inf

回答

3

你有的代碼很好。當編譯器意識到它可以優化掉臨時對象,而改爲構建新對象時,複製elision就會發生。 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(即不調用複製構造函數),而在第二種情況下,它必須調用複製構造函數並複製副本。

如果你的編譯器沒有利用拷貝elision/RVO,而不是C++ 11標準,返回的值是一個臨時的(在第一種情況下),並且由於類std :: string是可移動的,被移動。例如,

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

因此它並不總是顯而易見事先說,如果移動的語義將被使用,或者複製省略/ RVO會發生,這取決於你的編譯器。您可以強制搬遷的語義,如果你想,雖然,通過使用

std::move 

編輯:順便說一句,你甚至不會被允許返回引用到一個臨時的。你不能採用r值的參考(臨時)。

1

複製elision是一個簡單的編譯器優化,它是爲了避免在處理臨時對象時不必要的副本而實現的。在你的情況下,你不是返回一個臨時值,而是來自一個成員變量的值。既然你是通過價值來回報它,它會被複制。沒有辦法繞過它。