簡體   English   中英

抽象基類:如何為包含指向(抽象)基類的指針的類定義復制構造函數或賦值運算符?

[英]Abstract Base Classes: How do you define a copy constructor or assignment operator for a class that contains a pointer to a (abstract) base class?

我剛剛在parashift.com上遇到過關於c ++中抽象基類的問題。

作者提供了在Abstract Base Class中創建純虛擬成員函數Clone()的解決方案。該函數的目的是創建並返回ABC指向的克隆對象的地址。 在這里我有點困惑的是,如果我們在沒有這樣做的情況下實現相同的事情,那么創建這個虛函數和覆蓋Assignment操作符和復制構造函數的用途是什么。

class Shape {

    public:
      // ...
      virtual Shape* clone() const = 0;   // The Virtual (Copy) Constructor
      // ...
};

然后我們在每個派生類中實現這個clone()方法。 以下是派生類Circle的代碼:

class Circle : public Shape {

public:

  // ...
  virtual Circle* clone() const;
  // ...

};

Circle* Circle::clone() const
{
  return new Circle(*this);
}

現在假設每個Fred對象“都有一個”Shape對象。 當然,Fred對象不知道Shape是Circle還是Square還是...... Fred的復制構造函數和賦值運算符將調用Shape的clone()方法來復制對象:

class Fred {
public:
  // p must be a pointer returned by new; it must not be NULL
  Fred(Shape* p)
    : p_(p) { assert(p != NULL); }
 ~Fred()
    { delete p_; }
  Fred(const Fred& f)
    : p_(f.p_->clone()) { }
  Fred& operator= (const Fred& f)
    {
      if (this != &f) {              // Check for self-assignment
        Shape* p2 = f.p_->clone();   // Create the new one FIRST...
        delete p_;                   // ...THEN delete the old one
        p_ = p2;
      }
      return *this;
    }
  // ...
private:
  Shape* p_;
};

我認為我們可以在不重寫Assignment操作符或復制構造函數的情況下實現上述行為。 如果我們有兩個對象f1 (P_指向Circle)和f2 (P_指向Square)類型為Fred。 然后

f1=f2;  // This line exhibits the same behavior what  above code is doing. 

在默認情況下, f2 P_ (Square的地址)將被復制到P_f1 現在f1將指向Square。 我們唯一需要注意的是刪除Circle的對象,否則它將是懸空狀態。

為什么作者提到了上述技術來解決這個問題? 請指教。

你可以這樣做

delete f1.p;
f1 = f2;

但這意味着Fred類的用戶 - 不一定是它的作者 - 需要知道他必須先調用delete f1.p 現在對你來說可能很明顯,但是其他人會因為一個簡單的賦值導致內存泄漏而感到非常驚訝。 此外,如果你在很長一段時間后回到你的代碼,也許你自己忘記了這個小規則,並做錯了。

由於在分配Fred之前總是必須刪除形狀,因此在overriden equals運算符中寫入它是絕對明智的。 因此刪除會自動發生,用戶無需擔心。

編輯回答評論中的問題

基類中的virtual Shape *clone()函數強制每個派生類實現clone()函數。 如果從派生派生並忘記實現clone() ,則代碼將無法編譯。 這很好,因為Fred的重寫賦值運算符依賴於它。

在這種情況下,您需要制作Fred對象的深層副本。 由於析構函數執行了delete p_; ,如果你有兩個指向同一個Shape Fred對象,你會得到一個雙重自由錯誤。 clone()接口的原因是Fred不知道p_指向什么類型的對象,因此它無法直接調用正確的復制構造函數。 相反,它依賴於Shape的子類來創建自己的副本,並使用虛方法調度來創建正確類型的對象。

這個例子並沒有試圖將f2賦值給f1,所以OP認為f1 = f2; 表現出相同的行為是不正確的。 示例是將f2的副本分配給f1,因此行為更接近於f1 = new Whatever_f2_Is(*f2)

由於f2是指向基類的指針,因此此時沒有足夠的信息來知道要使用哪個復制構造函數(稍有​​說謊,但克隆方法仍然更容易使用)。 你不能調用new Shape(),即使shape不是純虛擬的,因此也是不可實例化的,因為shape不知道子類的額外信息。 你會有一個Shape,但會失去Circle-ness或Square-ness的所有額外方面。

幸運的是,雖然我們對f2的實際情況一無所知,但f2上的對象仍然知道它是什么,我們可以將副本的創建委托給它。 這就是克隆方法為您所做的。

另一種方法是玩游戲或使用ID代碼和工廠。

暫無
暫無

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

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