2015-02-24 45 views
4

在C++入門中,有一個使用複製控制成員來使一個類的行爲「有價值」的例子;即複製對象時,副本是獨立的。它提出了以下代碼:實施價值複製賦值運算符

class HasPtrValue 
{ 
public: 
    HasPtrValue(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { } 
    HasPtrValue(const HasPtrValue &orig) : ps(new std::string(*orig.ps)), i(orig.i) { } 
    HasPtrValue& operator=(const HasPtrValue&); 
    ~HasPtrValue() { delete ps; }; 

    std::string *ps; 
    int i; 
}; 

HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs) 
{ 
    auto newp = new std::string(*rhs.ps); 
    delete ps; 
    ps = newp; 
    i = rhs.i; 
    return *this; 
} 

我的問題是關於複製賦值運算符。據我瞭解它是在堆上創建一個新字符串,刪除舊字符串,並使lhs指向新的字符串。這真的有必要嗎?請問下面的代碼不是通過簡單地分配給堆上現有的字符串來完成同樣的事情嗎?

HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs) 
{ 
    *ps = *rhs.ps; 
    i = rhs.i; 
    return *this; 
} 
+2

整個類都是無用的,因爲它可以直接存儲'std :: string'並讓它自動管理。 – milleniumbug 2015-02-24 20:00:43

+4

你的觀察是正確的,儘管結尾的措辭有點奇怪。除了整個班級的無用之外,我認識到這是一個學術活動。你提出的改變將調用'std :: string'的複製賦值操作符,並直接賦值'int'成員'i'。前者的確在內部生活在一堆。對於*他們*賦值運算符的代碼的選擇實際上不是大多數人會這樣做的方式,因爲它是任何東西 - 但是對異常友好的,事實上,這些中沒有一個通常是這樣做的。 – WhozCraig 2015-02-24 20:03:27

+3

當然,我不會在實際的程序中使用它。這僅僅是爲了概念證明。 @milleniumbug – JamesLens 2015-02-24 20:04:18

回答

2

你是對的。 您的版本不僅可以工作,而且效率更高,因爲ps->capacity() >= rhs.ps->capacity()可以重複使用現有內存。

如果你想提供強異常保證您應該使用copy-and-swap idiom

HasPtrValue& HasPtrValue::operator=(HasPtrValue copy) // notice the by value 
{ 
    swap(*this, copy); 
    return *this; 
} 

// integrating tip from link posted by WhozCraig 
friend void swap(HasPtrValue &lhs, HasPtrValue &rhs) 
{ 
    using std::swap; 
    swap(lhs.ps, rhs.ps); 
    swap(lhs.i, rhs.i); 
} 

雖然你對代碼的更改應該已經提供強異常保證,只要i沒有被重新排序分配編譯器。

+0

對於OP的好處,關於[copy-swap idiom]背後想法的傑出問題和答案(http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom)。值得注意的是,這基本上做了第一個代碼的功能,但相當安全。 – WhozCraig 2015-02-24 20:14:50

+0

你和Vlad提出了一個很好的觀點,我沒有考慮ps-> capacity()> = rhs.ps-> capacity()時會發生什麼。實際上,內存被重用,但是當堆沒有足夠的連續內存來容納新的字符串長度時會發生什麼?如果字符串被重定位到另一個地方,指針是否不會被更新?謝謝你的複製交換成語,我會閱讀。 – JamesLens 2015-02-24 20:25:23

+0

'std :: string'在內部完成所有工作。它包含一個指向實際字符串數據的指針。如果沒有足夠的容量剩餘新的內存將被分配,如果成功,舊的將被刪除。但正如前面提到的那樣,所有的'std :: string'都是內部的,所以你不必爲此煩惱。這也是爲什麼你總是比較喜歡'std :: string'而不是c-strings的原因。 – mfuchs 2015-02-24 20:28:08

2

你是對的。這將足以定義拷貝賦值運算符以下方式

HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs) 
{ 
    *ps = *rhs.ps; 
    i = rhs.i; 
    return *this; 
} 

唯一的區別(除了分配一個新的內存),在這種情況下,字符串可以包含多少保留的內存雖然RHS的字符串對象可以足夠小。

當使用複製賦值運算符時,C++標準不會說目標字符串將縮小到原始字符串的大小。它只是說

size() str.size() 
capacity() a value at least as large as size() 

至於原始版本,它應該檢查是否有自我分配,以避免冗餘內存分配。那就是它應該看起來像

HasPtrValue& HasPtrValue::operator=(const HasPtrValue &rhs) 
{ 
    if (this != &rhs) 
    { 
     auto newp = new std::string(*rhs.ps); 
     delete ps; 
     ps = newp; 
     i = rhs.i; 
    } 

    return *this; 
} 
+0

檢查自我分配對我來說看起來很悲觀。 – mfuchs 2015-02-24 20:37:24