添加虚函数而不修改原始类

Pet*_*der 6 c++ oop polymorphism

假设我们已经有了类的层次结构,例如

class Shape { virtual void get_area() = 0; };
class Square : Shape { ... };
class Circle : Shape { ... };
etc.
Run Code Online (Sandbox Code Playgroud)

现在让我们说我想(有效地)在每个子类中添加一个适当定义的virtual draw() = 0方法Shape.但是,假设我想在不修改这些类的情况下这样做(因为它们是我不想改变的库的一部分).

最好的方法是什么?

我是否真的"添加"一个virtual方法并不重要,我只是想要一个指针数组的多态行为.

我的第一个想法是这样做:

class IDrawable { virtual void draw() = 0; };
class DrawableSquare : Square, IDrawable { void draw() { ... } };
class DrawableCircle : Circle, IDrawable { void draw() { ... } };
Run Code Online (Sandbox Code Playgroud)

然后分别用s和s 替换Squares和Circles的所有创建.DrawableSquareDrawableCircle

这是实现这一目标的最佳方式,还是有更好的东西(最好是保留Squares和Circles完整的东西).

提前致谢.

Dan*_*Dan 5

(我确实提出了进一步的解决方案......忍受我......)

(几乎)解决问题的一种方法是使用访问者设计模式.像这样的东西:

class DrawVisitor
{
public:
  void draw(const Shape &shape); // dispatches to correct private method
private:
  void visitSquare(const Square &square);
  void visitCircle(const Circle &circle);
};
Run Code Online (Sandbox Code Playgroud)

而不是这个:

Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method
Run Code Online (Sandbox Code Playgroud)

你会这样做:

DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);
Run Code Online (Sandbox Code Playgroud)

通常在访问者模式中,您将实现如下draw方法:

DrawVisitor::draw(const Shape &shape)
{
  shape.accept(*this);
}
Run Code Online (Sandbox Code Playgroud)

但是只有在Shape层次结构被设计为被访问时才有效:每个子类accept通过调用visitXxxxVisitor上的相应方法来实现虚方法.很可能它不是为此而设计的.

如果无法修改类层次结构以acceptShape(以及所有子类)添加虚拟方法,则需要一些其他方法来分派到正确的draw方法.一个naieve方法是这样的:

DrawVisitor::draw(const Shape &shape)
{
  if (const Square *pSquare = dynamic_cast<const Square *>(&shape))
  {
    visitSquare(*pSquare);
  }
  else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape))
  {
    visitCircle(*pCircle);
  }
  // etc.
}
Run Code Online (Sandbox Code Playgroud)

这样可行,但以这种方式使用dynamic_cast会受到性能影响.如果你能负担得起,那么它是一种易于理解,调试,维护等的简单方法.

假设存在所有形状类型的枚举:

enum ShapeId { SQUARE, CIRCLE, ... };
Run Code Online (Sandbox Code Playgroud)

并且有一个虚方法ShapeId Shape::getId() const = 0;,每个子类都会覆盖它以返回它ShapeId.那么你可以使用一个庞大的switch声明而不是dynamic_casts 的if-elsif-elsif进行调度.或者也许不switch使用哈希表.最好的情况是将此映射函数放在一个位置,这样您就可以定义多个访问者,而无需每次都重复映射逻辑.

所以你可能也没有getid()方法.太糟糕了.获取每种类型对象唯一的ID的另一种方法是什么?RTTI.这不一定优雅或万无一失,但您可以创建type_info指针的哈希表.您可以在某些初始化代码中构建此哈希表,也可以动态构建(或两者).

DrawVisitor::init() // static method or ctor
{
  typeMap_[&typeid(Square)] = &visitSquare;
  typeMap_[&typeid(Circle)] = &visitCircle;
  // etc.
}

DrawVisitor::draw(const Shape &shape)
{
  type_info *ti = typeid(shape);
  typedef void (DrawVisitor::*VisitFun)(const Shape &shape);
  VisitFun visit = 0; // or default draw method?
  TypeMap::iterator iter = typeMap_.find(ti);
  if (iter != typeMap_.end())
  {
    visit = iter->second;
  }
  else if (const Square *pSquare = dynamic_cast<const Square *>(&shape))
  {
    visit = typeMap_[ti] = &visitSquare;
  }
  else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape))
  {
    visit = typeMap_[ti] = &visitCircle;
  }
  // etc.

  if (visit)
  {
    // will have to do static_cast<> inside the function
    ((*this).*(visit))(shape);
  }
}
Run Code Online (Sandbox Code Playgroud)

可能是那里的一些错误/语法错误,我还没有尝试编译这个例子.我之前做过类似的事情 - 技术有效.我不确定您是否可能遇到共享库的问题.

我要补充的最后一件事是:无论你如何决定进行调度,建立一个访客基类可能是有意义的:

class ShapeVisitor
{
public:
  void visit(const Shape &shape); // not virtual
private:
  virtual void visitSquare(const Square &square) = 0;
  virtual void visitCircle(const Circle &circle) = 0;
};
Run Code Online (Sandbox Code Playgroud)