抽象基类:如何为包含指向(抽象)基类的指针的类定义复制构造函数或赋值运算符?

时间:2015-07-25 18:16:45

标签: c++ class abstract-class base

我刚遇到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}}(广场地址)将被复制到f2P_。现在f1将指向Square。我们唯一需要注意的是删除Circle的对象,否则它将是悬空状态。

为什么作者提到了上述技术来解决这个问题? 请指教。

3 个答案:

答案 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代码和工厂。