【问题标题】:Transform a list of pointers to base class将指针列表转换为基类
【发布时间】:2011-04-22 15:53:54
【问题描述】:

我有一个设计,我有一个std::list 的基本指针,我想将其转换为一个添加行为的并行列表。我遇到的问题是我试图用来进行转换的对象在调用它时不知道实际类型是什么。

很可能我遗漏了一些微妙的东西,并且有一个简单的解决方法。但是,如果这是一个设计缺陷(我在其他帖子中看到过这个建议),那么解决这个问题的适当方法是什么?

假设如下:

class Sprite { /* ... */ };
class Character : public Sprite {};
class Markup : public Sprite {};

这些被构造(基于一些输入)到std::list< Sprite * >。我想要做的是最终获取列表并将其转换为适合输出操作的并行结构。例如,给定:

class HTMLSprite { /* ... */ };
class HTMLCharacter : public HTMLSprite {};
class HTMLMarkup : public HTMLSprite {};

理想情况下,我想做类似的事情

std::transform(sprites.begin (), sprites.end (), html.begin (), HTMLConvert);

类似的东西

struct HTMLConvert_ {
  HTMLSprite * operator () (const Character * c) { return new HTMLCharacter (); }
  HTMLSprite * operator () (const Markup * c) { return new HTMLMarkup (); }
} HTMLConvert;

现在,我得到了错误

call of `(HTMLConvert) (const Sprite* const&)' is ambiguous
HTMLSprite* HTMLConvert::operator()(const Character*) const <near match>

这引出了我的问题。这个问题的最佳解决方案是什么 - 重新设计还是其他方式?

谢谢。

【问题讨论】:

  • 访客模式的经典问题。

标签: c++ polymorphism transform


【解决方案1】:

除了 JoshD 的建议之外,您还可以使用 visitor pattern 为其他转换敞开大门。

使用协变返回类型向 Sprite 层次结构添加方法 dispatch_visit

class Sprite{
    virtual HTMLSprite * dispatch_visit( HTMLConvert_ const &c ) const = 0;
};
class Character : public Sprite {
    virtual HTMLCharacter * dispatch_visit( HTMLConvert_ const &c ) const
        { return c( this ); }
};
class Markup : public Sprite {
    virtual HTMLMarkup * dispatch_visit( HTMLConvert_ const &c ) const
        { return c( this ); }
};

这允许每个对象通知转换器它的动态类型——本质上,动态分派到并行类型,甚至是并行类型层次结构。在编写代码时,其他所有内容都几乎可以正常工作……根据静态参数类型,从转换器的 operator()() 函数中选择最佳候选者。

哦,你需要在转换器中添加“缺失函数”:

struct HTMLConvert_ {
  HTMLSprite * operator () (const Sprite * c) { return c->dispatch_visit( *this ); }
  HTMLCharacter * operator () (const Character * c) { return new HTMLCharacter (); }
  HTMLMarkup * operator () (const Markup * c) { return new HTMLMarkup (); }
} HTMLConvert;

嗯,那个重复的函数可以被一个模板封装……如果你想让visitable模板自动确定dispatch_visit的返回类型,这似乎只能在C++0x中工作。如果您不喜欢principal_base,可以将其排除在外。

#include <functional>

template< class T >
struct principal_base
    { typedef void type; };

template< class Client, class Visitor,
    class Base = typename principal_base< Client >::type >
struct visitable :
    virtual visitable< typename principal_base< Client >::type, Visitor > {
    virtual typename std::result_of< Visitor( Client * ) >::type
    dispatch_visit( Visitor const &v ) const
        { return v( static_cast< Client const * >( this ) ); }
};

template< class Client, class Visitor >
struct visitable< Client, Visitor, void > {
    virtual typename std::result_of< Visitor( Client * ) >::type
    dispatch_visit( Visitor const &v ) const = 0;
};

class HTMLSprite { /* ... */ };
class HTMLCharacter : public HTMLSprite {};
class HTMLMarkup : public HTMLSprite {};

class Sprite;
class Character;
class Markup;

struct HTMLConvert_ {
  HTMLSprite * operator () (const Sprite * c);
  HTMLCharacter * operator () (const Character * c);
  HTMLMarkup * operator () (const Markup * c);
} HTMLConvert;

class Sprite : public visitable< Sprite, HTMLConvert_ > {};
template<> struct principal_base< Character >
    { typedef Sprite type; };
class Character : public Sprite, visitable< Character, HTMLConvert_ > {};
template<> struct principal_base< Markup >
    { typedef Sprite type; };
class Markup : public Sprite, visitable< Markup, HTMLConvert_ > {};

//class Invalid : Character, Markup {};

HTMLSprite * HTMLConvert_::operator () (const Sprite * c)
    { return c->dispatch_visit( *this ); }
HTMLCharacter * HTMLConvert_::operator () (const Character * c)
    { return new HTMLCharacter (); }
HTMLMarkup * HTMLConvert_::operator () (const Markup * c)
    { return new HTMLMarkup (); }

【讨论】:

  • 这看起来像我最终可能会做的事情。不幸的是,我无法使用模板化虚拟 dispatch_visit 来尝试避免冗余。我很快就进入了我对 C++ 语言理解的极限。
  • @ezpz:我不明白为什么模板化dispatch_visit 应该是不可能的;我试图为这个答案想出一个,但被困在需要decltype/typeof 运算符。因此,无论如何,在 C++0x 中它更容易。 (不能有模板化的虚函数,但可以在模板化的基类中有虚函数。)
  • @ezpz:用这样的模板更新。确实需要 C++0x,但你可以解决这个问题。
【解决方案2】:

抽象工厂怎么样?

       +-----------------+                            +--------+         
       |  RenderFactory  |                            | Sprite |
       |=================|                            +--------+
       | CreateSprite()  |                                 /\
       | CreateMarkup()  |                         +------------------+
       +-----------------+                         |                  |
               /\                            +------------+    +-------------+
       +-----------------+              ....>| HTMLSprite |    | PlainSprite |
       |                 |              :    +------------+    +-------------+
+----------------+   +----------------+ :
| PlainFactory   |   |  HTMLFactory   | :
|================|   |================| :                 +--------+
| CreateSprite() |   | CreateSprite() |.:...              | Markup |
| CreateMarkup() |   | CreateMarkup() |..  :              +--------+
+----------------+   +----------------+    :                  /\
                                           :          +----------------+
                                           :          |                |
                                           :    +------------+   +-------------+
                                            ..> | HTMLMarkup |   | PlainMarkup |
                                                +------------+   +-------------+

【讨论】:

  • 哇 - ascii-licious 绘图 :) 虽然,这看起来像我在解析阶段需要做的工作加倍。这个想法是我将只解析一次,并允许通过此转换获得多种输出形式。
【解决方案3】:

由于您的转换需要了解要转换的所有类型,因此它也在执行映射功能。

这是我的快速破解

struct HTMLConvert_ {
  HTMLSprite * operator () (const Sprite* const& sp ) { 
    Character const * c = dynamic_cast<Character const *>(sp);
    if( c )
      return new HTMLCharacter (c);

    Markup const * m = dynamic_cast<Markup const *>(sp);
    if( c )
      return new HTMLMarkup (m);
  }
} HTMLConvert;

【讨论】:

  • 这很快变成了一个 RTTI switch 语句。在许多类型的情况下,这并不是最好的 - 我应该提到我有很多类型:)
【解决方案4】:

我建议将转换为 HTML 类型的函数添加到每个类。因此,您将拥有一个虚拟的 Convert 函数作为 Sprite 的成员,并且每个派生类都可以覆盖它。然后,当您调用Sprite *s 列表中的Convert 函数时,它将调用相应的转换器。您需要转发声明返回类型 (HTMLSprite)。

可能有更优雅的方式,但这个想法允许您使用虚函数。

您的建议的问题是数组中的指针都是Sprite * 类型,无论它们实际指向什么,所以Sprite * 将传递给您的函数。相反,使用 mem_fun 之类的东西来创建一个将调用成员的结构;这将通过虚拟调用调用适当的函数:

std::transform(sprites.begin (), sprites.end (), html.begin (), mem_fun(&Sprite::Convert));

如果您需要我澄清任何事情,请发表评论。

【讨论】:

  • 我看到的问题是现在我的Sprite 类必须知道任何潜在的转换目标。我希望公开一个接口并支持对任意表示的更改,而无需让内部结构了解此类转换的细节。
  • 您可能需要为此跳过一些障碍。我知道这并不理想,但要正确转换课程,您需要私人访问权限。这意味着成员或朋友(或完全公开的类),其中任何一个都必须在类定义中声明。完全公开的类可能是最干净的选择,但您仍然需要一种方法来识别 Sprite * 实际指向的类型。这可以通过whatAmI 虚函数来完成。但是,就像我说的,它很快就会变得混乱。这里的其他人可能有一个优雅的解决方案;我会对它感兴趣。
  • @ezpz:然后您可以为每种转换使用并行特征类,但仍然使用访问者模式的多态性。 en.wikipedia.org/wiki/Visitor_pattern
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 2015-08-12
  • 2016-10-10
相关资源
最近更新 更多