【问题标题】:How should I distinguish between subclasses我应该如何区分子类
【发布时间】:2009-02-22 18:41:09
【问题描述】:

我有一个看起来像这样的令牌类:

class Token
{
 public:
   typedef enum { STRTOK, INTTOK } Type;
   virtual bool IsA(Type) = 0;
}

class IntTok : public Token
{
   int data;
 public:
   bool IsA(Type t) { return (t == INTTOK); }
   int GetData() { return data; }
}

IntTok newToken;
if ( newToken.IsA(Token::INTTOK )
{
  //blah blah
}

所以基本上我必须在 Token 类中定义每个子类;结果并没有那么糟糕,因为子类很少,我无法想象它们会发生变化。但是,与使用动态转换识别子类相比,它仍然丑陋、笨拙且“正确”。然而:

IntTok newToken;
IntTok* tmpTokenTest = dynamic_cast<IntTok*>(&newToken);
if ( tmpTokenTest != NULL )
{
  //blah blah
}

也很笨拙。特别是当我必须将它们串在一个大的嵌套 if 中时。

那么你会用哪个?这个问题有其他解决方案吗?

注意:我知道无论如何我都必须强制他们获取各自的数据,但是

  1. 在我使用它们的功能之前我不会投射它们,所以感觉更干净,并且
  2. 我测试他们的类型比使用他们的数据更频繁。

注意2:上面代码中没有说明的是这些token也是一个链表。这使得模板变得困难(Token&lt;int&gt; 可能指向Token&lt;string&gt; 等)。这就是为什么我需要一个 Token 类作为父类开始的原因。

【问题讨论】:

    标签: c++ inheritance coding-style dynamic-cast


    【解决方案1】:

    只需使用虚函数来做你想做的事。而不是这个:

    if(newToken.IsA(Token::INTTOK))
    {
        // do stuff with ((IntTok*)&newToken)->GetData()
    }
    

    这样做:

    class Token
    {
    public:
        ...
        virtual void doTypeDependentStuff() {}  // empty default implementation
    }
    
    class IntTok : public Token
    {
    public:
        ...
        void doTypeDependent()
        {
            // do stuff with data
        }
    }
    

    【讨论】:

    • 这不是真正的“做”情况。这些标记被其他对象解析,所以我只需要查看它们是什么类型并获取它们的存储值。
    • @Whaledawg:你可以控制那些“其他对象”,对吧?所以你让 那些 对象调用虚函数。如果您考虑一下,任何时候您都可以编写“if (x.IsA(INTTOK)) { XYZ; }”,您可以改为编写一个虚函数 do_XYZ(),并将其称为“x .do_XYZ();".
    【解决方案2】:

    Visitor pattern,确实如此。

    class TokenVisitor {
    public:
        virtual ~TokenVisitor() { }
        virtual void visit(IntTok&) = 0;
        virtual void visit(StrTok&) = 0;
    };
    
    class Token {
     public:
       virtual void accept(TokenVisitor &v) = 0;
    };
    
    class IntTok : public Token {
       int data;
     public:
       virtual void accept(TokenVisitor &v) {
           v.visit(*this);
       }
       int GetData() { return data; }
    };
    

    然后只需实现访问者接口并调用

    token->accept(myVisitor);
    

    控制权将交给访问者,然后访问者可以执行适当的操作。如果您需要在本地使用正确类型的变量 - 那么您将很难绕过向下转换它。但我认为使用虚函数驱动对特定实现的控制通常是解决它的好方法。

    【讨论】:

    • 如果您需要在本地拥有正确类型的变量 - 那么您将很难绕过它。 我认为我做不到。我需要从令牌中获取数据,即使有访问者,返回类型也需要在 C++ 中进行静态处理。 ://
    • 是的,这就是我的意思。当您使用访问者时,访问者将被称为脱离上下文。如果您想在本地拥有变量 - 如果不确定类型并相应地分支,这是不可能的。
    • 即使是 boost::variant 的 boost 访问者也不能这样做 :) 访问者方法都必须有一个共同的返回类型。但是,是什么导致您无法在访问者方法中处理您的内容?你能详细说明一下吗?
    • 好吧,它目前的工作方式是 Lexer 创建这个标记列表并将其传递给解析器,解析器将根据类型和内容构建解析树。我也许可以让解析器成为访问者,我得考虑一下。
    • 虽然 iirc,但通常的做法是使用枚举的东西,并使用一些自动机(我相信是下推自动机左右)对这些令牌做一些事情。但我自己从来没有做过:/
    【解决方案3】:

    我是否建议使用 Boost::Variant,它基本上是多种类型的联合(variant 类型的对象可以包含 Ti (1

    使用它,您将不必使用继承。

    更多信息请参见there

    【讨论】:

      【解决方案4】:

      所以基本上我必须在 Token 类中定义每个子类

      你能解释一下原因吗?

      真的有必要投吗?可以使用多态函数。

      或者,也许您可​​以拥有一个模板化的 Token 类(对某些具有默认行为)并专门处理其余的。

      【讨论】:

        【解决方案5】:

        这是一个令人讨厌的问题,尽管我更有可能使用使用 RTTI 的版本。

        新的 C++ 编译器(我上次在 VC 6.0 中尝试过,当时它并没有真正受支持)难道不是假设 typeid 运算符这样您就不需要完整的动态转换了吗?

        【讨论】:

        • 我从没听说过 typeid 运算符:/
        • C++ 标准的第 5.2.8 节。甚至还有一个 Wikipedia 条目:en.wikipedia.org/wiki/Typeid 话虽如此,但不确定它是否受支持。
        猜你喜欢
        • 2016-11-26
        • 2013-07-17
        • 2018-01-26
        • 1970-01-01
        • 2019-10-05
        • 1970-01-01
        • 1970-01-01
        • 2015-03-09
        • 1970-01-01
        相关资源
        最近更新 更多