草庐IT

c++ - 在不修改原有类的情况下添加虚函数

coder 2024-02-02 原文

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

class Shape { virtual void get_area() = 0; };
class Square : Shape { ... };
class Circle : Shape { ... };
etc.

现在假设我想(有效地)向 Shape 添加一个 virtual draw() = 0 方法,并在每个子类中使用适当的定义。 但是,假设我想在不修改这些类的情况下执行此操作(因为它们是我不想更改的库的一部分)。

解决此问题的最佳方法是什么?

我是否真的“添加”了一个 virtual 方法并不重要,我只想要给定一个指针数组的多态行为。

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

class IDrawable { virtual void draw() = 0; };
class DrawableSquare : Square, IDrawable { void draw() { ... } };
class DrawableCircle : Circle, IDrawable { void draw() { ... } };

然后用 DrawableSquareDrawableCircle 替换所有 SquareCircle 的创建,分别。

这是完成此任务的最佳方法,还是有更好的方法(最好是让 SquareCircle 的创建完好无损)。

提前致谢。

最佳答案

(我确实提出了进一步的解决方案......请耐心等待......)

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

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

然后代替这个:

Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method

你会这样做:

DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);

通常在访问者模式中,您会像这样实现 draw 方法:

DrawVisitor::draw(const Shape &shape)
{
  shape.accept(*this);
}

但这只有在 Shape 层次结构设计为可访问的情况下才有效:每个子类通过调用适当的 visitXxxx 来实现虚拟方法 accept访问者的方法。它很可能不是为此而设计的。

如果无法修改类层次结构以将虚拟 accept 方法添加到 Shape(以及所有子类),您需要一些其他方式来分派(dispatch)到正确的 绘制方法。一种天真的方法是这样的:

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.
}

这会起作用,但是以这种方式使用 dynamic_cast 会影响性能。如果您负担得起,这是一种简单易懂的方法,易于理解、调试、维护等。

假设有一个所有形状类型的枚举:

enum ShapeId { SQUARE, CIRCLE, ... };

还有一个虚拟方法 ShapeId Shape::getId() const = 0; 每个子类都会覆盖它以返回其 ShapeId。然后,您可以使用大量的 switch 语句而不是 dynamic_cast 的 if-elsif-elsif 来进行分派(dispatch)。或者也许可以使用哈希表代替 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);
  }
}

可能存在一些错误/语法错误,我还没有尝试编译这个例子。我以前做过类似的事情——这项技术很管用。不过,我不确定您是否会遇到共享库问题。

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

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;
};

关于c++ - 在不修改原有类的情况下添加虚函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2352533/

有关c++ - 在不修改原有类的情况下添加虚函数的更多相关文章

  1. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  2. ruby - 将 Bootstrap Less 添加到 Sinatra - 2

    我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - 默认情况下使选项为 false - 2

    这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb

  5. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  6. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  7. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  8. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  9. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  10. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

随机推荐