我刚遇到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.
在默认情况下,P_
的{{1}}(广场地址)将被复制到f2
到P_
。现在f1
将指向Square。我们唯一需要注意的是删除Circle的对象,否则它将是悬空状态。
为什么作者提到了上述技术来解决这个问题? 请指教。
答案 0 :(得分:1)
确实可以做到
delete f1.p;
f1 = f2;
但这意味着Fred类的用户 - 不一定是其作者 - 需要知道他必须先调用delete f1.p
。现在对你来说可能很明显,但是其他人会因为一个简单的赋值导致内存泄漏而感到非常惊讶。此外,如果你在很长一段时间后回到你的代码,也许你自己忘记了这个小规则,并做错了。
由于总是必须在分配Fred之前删除形状,因此在overriden equals运算符中写入它是绝对明智的。因此删除会自动发生,用户无需担心。
编辑回答评论中的问题:
基类中的virtual Shape *clone()
函数强制每个派生类实现clone()函数。如果从派生派生而忘记实现clone()
,则代码将无法编译。这很好,因为Fred的重写赋值运算符依赖于它。
答案 1 :(得分:1)
在这种情况下,您需要制作Fred
对象的深层副本。由于析构函数执行delete p_;
,如果有两个Fred
个对象指向同一个Shape
,则会出现双重自由错误。 clone()
接口的原因是Fred
不知道对象p_
指向的是什么类型,因此它无法直接调用正确的复制构造函数。相反,它依赖于Shape
的子类来创建自己的副本,并使用虚拟方法分派来创建正确类型的对象。
答案 2 :(得分:0)
该示例并未尝试将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代码和工厂。