為什麼GCC5.x.x版本中的std::string不再採用寫時複製的實現,而改用了SSO的實現?
註:SSO為small string optimization縮寫。
簡單地說就是從C++11開始不允許 std::string 用 Copy On Write 的實現。
cow會帶來意外的開銷,比如[]觸發深拷貝。要避免這個就要引入額外的心智負擔。
cow主要解決的是傳參問題,但自從有右值引用和move語意以後,這個最大的優勢已經不復存在。
剩下的,只有跨線程傳遞只讀對象時有價值了,但同樣可以用shared_ptr實現。所以,cow其實是語言機制不完善時的一種tradeoff,在現代c++里已經可以拋棄。然而qt已經全面實裝cow,c++11出了以後略尷尬……問了幾位大佬,我個人的看法是:
」COW對多線程不友好「是對的,可以看這篇Herb的文章Optimizations That Arent (In a Multithreaded World),「COW達不到const成員的thread safety」是不準確的。
典型的COW實現一般要用到原子的refcnt。const成員直接讀取,non-const成員需要判定refcnt的值,如果大於1就做一份拷貝再在副本上修改,這樣線程就是安全的。Qt就大量採用了COW的設計來拷貝和發送信號。對多線程不友好的主要原因是,即便refcnt是原子的,拷貝操作仍需要加鎖保護。比如如下例子:
class string{
Proxy* data;
};
class Proxy{
mutable atomic_t refcnt;
size_t capacity;
size_t length;
char varlen[1];
};
void string::wrong_write_method(){
//WRONG, not thread-safe
if (data-&>refcnt &> 1 ){
data = make_a_copy(data);
}
do_write(data);
}
void string::wrong_read_method() const {
data-&>refcnt++;
do_read(data);
data-&>refcnt--;
}
這種問題無法從外界修復,只能讓標準庫自己加一個鎖,這就造成了pay for what you dont need.
此外,COW可能引入意外的拷貝。試想用一個operator[]都能觸發拷貝,時間未必是O(1)了,寫程序礙手礙腳,這等於增大了心智負擔。
另外據說lyp和chvim聚聚指出,COW還有noexcept的問題。
C++11要求stl容器的實例可以線程安全地多線程同時讀取,cow達不到這個要求
也許我沒說清楚,C++11要求stl的const成員函數是線程安全的
懶得翻標準了,給一個Andrei Alexanderescu的某個slides的截圖
我就說一說noexcept的問題。只要是非POD數據照理來說就無法做到拷貝是noexcept的。如果一大堆有就地修改功能的成員函數都是有可能拷貝數據的,那麼就也無法做到noexcept。
按照現在現代C++的設計思路的話,最好是一個類的就地訪問函數是noexcept的,移動是noexcept的,然後拷貝是有可能丟異常的。這樣會比較爽。
印象中五點幾有一個版本還是 copy on write. 曾經導致我們系統產生過一點延遲
推薦閱讀:
※如何用 C/C++ 求 1 到 1000 內的所有完全數?
※reinterpret_cast把const string*轉換成const char*出錯?
※為什麼用了using namespace std會報錯?
※c++如何在編譯期判斷一個對象是否是字元串字面值?
※C++的std::thread是怎麼進行參數傳遞的?