[英]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.