簡體   English   中英

為類數據成員調用副本構造函數和operator =

[英]calling copy constructor and operator= for class data members

以下是示例代碼(僅用於學習目的)。 類A和B是獨立的,並且具有復制構造函數和operator =。

class C
{

public:
   C(string cName1, string cName2): a(cName1), b(new B(cName2)) {}
   C(const C &c): a(c.a), b(new B(*(c.b))) {}
   ~C(){ delete b; }
   C& operator=(const C &c)
   {
      if(&c == this) return *this;

      a.operator=(c.a);

      //1
      delete b;
      b = new B(*(c.b));

      //What about this:
      /*

      //2
      b->operator=(*(c.b));

      //3
      (*b).operator=(*(c.b));

      */

      return *this;
   }

private:
   A a;
   B *b;

};

有三種方法可以為數據成員b賦值。 實際上,它們首先調用復制構造函數。 我應該使用哪一個? // 2和// 3似乎是等效的。

我決定將答案移至答案並進行詳細說明。

您要使用2或3,因為1會完全重新分配對象。 您需要清理所有工作,然后再進行所有工作以重新分配/重新初始化對象。 但是副本分配:

* b = * cb;

您在代碼中使用的變體僅復制數據。

但是,我們必須問,為什么您首先要這樣做?

我認為將指針作為類的成員有兩個原因。 第一種是使用b作為不透明指針。 如果真是這樣,那么您無需繼續閱讀。

但是,更有可能的是您嘗試將多態與b一起使用。 IE中有從B繼承的D和E類。在這種情況下,您不能使用賦值運算符! 這樣考慮:

B* src_ptr = new D();//pointer to D
B* dest_ptr = new E();//pointer to E
*dest_ptr = *src_ptr;//what happens here?

怎么了?

好了,編譯器看到了使用賦值運算符的以下函數調用:

B& = const B&

它只知道B的成員:它無法清理E不再使用的成員,並且它實際上不能從D轉換為E。

在這種情況下,通常最好使用情況1而不是嘗試確定子類型並使用克隆類型運算符。

class B
{
public:
  virtual B* clone() const = 0;
};

B* src_ptr = new E();//pointer to D
B* dest_ptr = new D();//pointer to E, w/e
delete dest_ptr;
dest_ptr = src_ptr->clone();

這可能取決於示例,但實際上我什至看不到為什么b被分配在堆上。 但是,為什么在堆上分配b的原因說明了如何復制/分配b。 我認為將對象分配在堆上而不是嵌入或分配在堆棧上的原因有三個:

  1. 該對象在多個其他對象之間共享。 顯然,在這種情況下,存在共享所有權,並且不是實際復制的對象,而是指向該對象的指針。 最有可能使用std::shared_ptr<T>維護對象。
  2. 該對象是多態的,支持的類型集未知。 在這種情況下,實際上不是復制對象,而是使用基類中的自定義虛擬clone()函數clone() 由於從中分配的對象的類型不必相同,因此復制構造和分配實際上都會克隆該對象。 該對象可能是使用std::unique_ptr<T>或自定義clone_ptr<T> ,后者會自動處理適當的類型克隆。
  3. 該對象太大,無法嵌入。 當然,除非您恰好實現了大對象並為其創建了合適的句柄,否則這種情況不會真正發生。

在大多數情況下,我實際上會以相同的形式實現賦值運算符:

T& T::operator=(T other) {
    this->swap(other);
    return *this;
}

也就是說,對於分配對象的實際副本,代碼將利用已經編寫的副本構造函數和析構函數(實際上都可能是= default ed)加上一個swap()方法,該方法僅在兩個對象之間交換資源(假設分配器相等) ;如果您需要使用非相等分配器,那么事情會變得更加有趣)。 像這樣實現代碼的好處是,分配是強異常安全的。

回到您的分配方法:在任何情況下,我都不會先delete對象,然后分配替換對象。 另外,我將從所有可能失敗的操作開始,將它們放在適當的位置:

C&C :: operator =(C const&c){std :: unique_ptr tmp(new B(* cb)); this-> a = ca; this-> b = tmp.reset(this-> b); 返回* this; }

請注意,此代碼不會做一個自我賦值檢查。 我聲稱,任何實際上僅通過顯式防范而僅用於自我分配的賦值運算符都不是異常安全的,至少,它不是絕對異常安全的。 提出基本保證的理由比較困難,但是在大多數情況下,我已經看到分配不是基本的異常安全,問題中的代碼也不例外:如果分配拋出, this->b包含一個陳舊的指針,可以不能從另一個指針告訴它(至少在delete b;在分配之前,至少需要將其設置為nullptr )。

  b->operator=(*(c.b));
  (*b).operator=(*(c.b));

這兩個操作是等效的,應該拼寫

  *this->b = *c.b;

要么

  *b = *c.b;

我更喜歡限定版本,例如,因為即使b是從模板化基礎繼承的模板的基類,它也可以工作,但是我知道大多數人不喜歡它。 如果對象的類型恰好是內置類型,則使用operator=()失敗。 但是,對堆分配的對象進行簡單分配沒有任何意義,因為如果該對象確實做了正確的事情,則應該在堆上分配該對象。

如果使用方法1,則賦值運算符甚至不提供基本(例外)保證,因此可以肯定。

最好當然是由價值組成。 然后,您甚至不必編寫自己的副本分配運算符,讓編譯器為您完成!

下一個最好的選擇是,將其分配給現有對象,因為它似乎總是有一個有效的b指針: *b = *cb;

a = c.a;
*b = *c.b;

當然,如果b可能是空指針,則代碼應在第二行賦值之前進行檢查。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM