2014-06-16 79 views
1

我知道,除非另有說明,接受右值引用參數的所有標準庫函數都保證以有效但未指定的狀態離開移動的參數,並且某些這裏的例子可能會出現未定義的行爲,但潛在的問題並不依賴於此。字符串類成員的移動構造函數的行爲

下面的程序:

// testmove1.cpp 

#include <iostream> 
#include <string> 

int main() { 
    std::string s1{"String"}; 
    std::cout << "s1: [" << s1 << "]" << std::endl; 
    std::string s2{std::move(s1)}; 
    std::cout << "s2: [" << s2 << "]" << std::endl; 
    std::cout << "s1 after move: [" << s1 << "]" << std::endl; // Undefined 

    return 0; 
} 

輸出:

[email protected]:~$ ./testmove1 
s1: [String] 
s2: [String] 
s1 after move: [] 
[email protected]:~$ 

1.4.3 s1此舉似乎未定義給我後,但被空字符串是至少一個可行的選擇。 Valgrind報告爲這個計劃做了一個單獨的分配,這是你所期望的。

如果我做一些非常相似,但有一類成員,我得到不同的結果:

// testmove2.cpp 

#include <iostream> 
#include <string> 

class MyClass { 
    std::string m_data; 

    public: 
     MyClass(const std::string& data) : 
      m_data(data) {} 
     MyClass(const MyClass&& other) : 
      m_data{std::move(other.m_data)} {}; 
     const std::string& get_data() const { return m_data; } 
}; 

int main() { 
    MyClass c1{"Object"}; 
    std::cout << "c1: [" << c1.get_data() << "]" << std::endl; 
    MyClass c2{std::move(c1)}; 
    std::cout << "c2: [" << c2.get_data() << "]" << std::endl; 
    std::cout << "c1 after move: [" << c1.get_data() << "]" << std::endl; 

    return 0; 
} 

與輸出:

[email protected]:~$ ./testmove2 
c1: [Object] 
c2: [Object] 
c1 after move: [Object] 
[email protected]:~$ 

具有第二串清除出去也似乎像一個可行的選擇,所以這本身並不令人驚訝。但令我驚訝的是,這種行爲與純粹將字符串放入類中的結果完全不同。 Valgrind也報告了在這裏進行的單一分配。

爲了測試他們是否實際上是指向同一個東西,搬家後,我可以改變c2,並檢查是否c1也得到改變:

// testmove3.cpp 

#include <iostream> 
#include <string> 

class MyClass { 
    std::string m_data; 

    public: 
     MyClass(const std::string& data) : 
      m_data(data) {} 
     MyClass(const MyClass&& other) : 
      m_data{std::move(other.m_data)} {}; 
     const std::string& get_data() const { return m_data; } 
     void change_data() { m_data[0] = 'A'; } 
}; 

int main() { 
    MyClass c1{"Object"}; 
    std::cout << "c1: [" << c1.get_data() << "]" << std::endl; 
    MyClass c2{std::move(c1)}; 
    std::cout << "c2: [" << c2.get_data() << "]" << std::endl; 
    std::cout << "c1 after move: [" << c1.get_data() << "]" << std::endl; 

    c2.change_data(); 
    std::cout << "c1 after change: [" << c1.get_data() << "]" << std::endl; 
    std::cout << "c2 after change: [" << c2.get_data() << "]" << std::endl; 

    return 0; 
} 

,輸出:

[email protected]:~$ ./testmove3 
c1: [Object] 
c2: [Object] 
c1 after move: [Object] 
c1 after change: [Object] 
c2 after change: [Abject] 
[email protected]:~$ 

在這裏,這兩個對象顯然不是指向相同的東西,因爲更改c2不會影響存儲在c1中的內容。 Valgrind現在報告了2個分配,這對於解釋所觀察到的行爲顯然是必要的,因爲我們顯然有兩個不同的字符串,但對於我來說,爲什麼突然之間因爲改變其中的一個而忽略了2個分配。如果我完全擺脫了這一舉措,只需創建c1,然後撥打change_data()即可,因此我只會按照您的預期獲得1次分配。

我們可以擺脫未定義行爲(不包括在我的一部分的任何其他錯誤)通過移除後移的所有訪問在c1

// testmove4.cpp 

#include <iostream> 
#include <string> 

class MyClass { 
    std::string m_data; 

    public: 
     MyClass(const std::string& data) : 
      m_data(data) {} 
     MyClass(const MyClass&& other) : 
      m_data{std::move(other.m_data)} {}; 
     const std::string& get_data() const { return m_data; } 
     void change_data() { m_data[0] = 'A'; } 
}; 

int main() { 
    MyClass c1{"Object"}; 
    std::cout << "c1: [" << c1.get_data() << "]" << std::endl; 
    MyClass c2{std::move(c1)}; 
    std::cout << "c2: [" << c2.get_data() << "]" << std::endl; 

    c2.change_data(); 
    std::cout << "c2 after change: [" << c2.get_data() << "]" << std::endl; 

    return 0; 
} 

,輸出:

[email protected]:~$ ./testmove4 
c1: [Object] 
c2: [Object] 
c2 after change: [Abject] 
[email protected]:~$ 

我們可以顯然不再看到c1沒有改變,因爲我們沒有輸出它。但Valgrind仍然顯示2個分配。

任何人都知道發生了什麼,在這裏?

  1. 爲什麼一個std::string似乎歸零以下的舉動時,它的本身,而不是在它的類成員?

  2. 在最後一個例子中,當我移動一個對象然後改變它時,爲什麼我得到兩個分配而不是一個,當我移動對象時只有一個分配,然後不改變它?我知道我們似乎正朝着量子計算的方向發展,但在C++中使用不確定性原理似乎還爲時過早。

我用G ++ 4.7.2,但我得到譁 - 503.0.40同樣觀察到的行爲。

編輯:思前想它,如果你最終在有效狀態的兩個對象,這有一定道理他們都擁有分配。這只是編譯器在確定其中一個永遠不會被使用的時候優化掉其中一個分配嗎?如果是的話,構建一個最小的例子是一個令人討厭的危險。

+1

如果某事物處於有效狀態,那麼檢查它就不是未定義的行爲。也許你的意思是*未指定*? –

+0

@MattMcNabb:你可能是對的,只需將它發送到'std :: cout'可能不涉及任何具有前提條件的函數。我的意思是沒有定義,因爲像'm_data [0]'這樣的移動後會有資格,但是這裏發生的事情可能不屬於這個類別。在任何情況下,這個問題都沒有密切關係。 –

回答

4

我認爲這是由於:

MyClass(const MyClass&& other) : 
     ^^^^^ 

由於勢必other對象無法通過此引用改變,預期移動操作的效果原來只是一個副本。如果我刪除此const則行爲變回你所期望的:

$ g++ -o tm3 tm3.cc -std=c++11 && ./tm3 
c1: [Object] 
c2: [Object] 
c1 after move: [] 
+0

啊哈。對於第一個問題來說,這是一個看似合理並且事後明顯的答案。 –

+1

我不確定標準是否實際要求這種行爲(無論是在常量還是非常量情況下),希望其他人可以使用某些標準引號進行跳轉。 –

0

廣告1.我懷疑是MyClass c2{std::move(c1)};調用默認的拷貝構造函數作爲 std::move(c1)導致右值引用,而不是一個常量rvalue-參考。 MyClass的默認拷貝構造函數然後調用std :: string的拷貝構造函數。

如果,然而MyClass(const MyClass&& other)叫,那就不是要求的std :: string的移動構造函數,如std::move(other.m_data)的類型爲const std::string &&

不同類型引用的匹配規則不易記憶和應用,但是實現資源傳輸的典型模式是使用非const的右值引用作爲參數的構造函數,因爲資源是打算的被帶走。

廣告2.的std :: string通常與參考計數和複製上的寫操作實現(如果多個引用已經計數)。這將解釋你的觀察。

+1

copy-on-write'std :: string'是C++ 11中的[不允許](http://stackoverflow.com/questions/12199710/legality-of-cow-stdstring-implementation-in-c11)儘管可能不合規的實現仍然存在) –