C ++ - 好的或坏的做法?

时间:2014-02-20 09:57:12

标签: c++ object casting polymorphism introspection

我正面临这样的情况:我发现在基类中存储对象的类型(作为枚举)以便进一步将指向该基类的指针转换为子类指针,具体取决于它的值类型。

例如:

class CToken
{
public:
   token_type_e eType;
};

class COperatorToken : public CToken
{
public:
    // Sub class specific stuff
};

class CLiteralToken : public CToken
{
public:
    // Sub class specific stuff
};

然后

vector<CToken *> aTokens;

//...

for( size_t nI = 0, nMaxI = aTokens.size(); nI < nMaxI; ++nI )
{
   switch( aTokens[ nI ]->eType )
   {
   case E_OPERATOR :
      // Do something with sub-class specific stuff.
      break;
   case E_LITERAL :
      // Do something with sub-class specific stuff.
      break;
   }
}

这是一种不好的做法吗?

谢谢:)

修改

说我正在分析我的令牌列表。在某些时候,我想要检查当前令牌是否是一个操作员,正如人们所建议的那样,使用虚拟函数virtual bool isOperator()。 现在,如果它是一个运算符,我将要访问该子类特定的东西,以找出,例如,它是哪种类型的运算符。在那种情况下,我该怎么办?我无法在我的基类中添加方法getOperatorType(),这是没有意义的。除了转换为子类以检索该子类成员值之外,还有其他方法吗?

5 个答案:

答案 0 :(得分:8)

类型字段确实破坏了C ++的面向对象特性。通常,您可以使用多态解决此问题:

CToken中定义一个函数virtual doSomething(),带有适当的参数和返回类型。

COperatorTokenCLiteralToken中实施该功能。

如果您使用aTokens[ nI ]->doSomething();

,运行时将调用相应的函数

答案 1 :(得分:3)

听起来像是在试图模仿代数数据类型。通常,你不会这样做,而是通过在基类上放置一个纯virtual函数并在派生类的覆盖中实现实际行为。

此外,如果您确实需要您提出的模式,语言运行时会知道类型,因此您无需存储它:

#include <iostream>
#include <typeinfo>

class Token { };

class Operator : public Token { };

class Literal : public Token { };

int main()
{
    Token *tok = new Literal();

    if (typeid(*tok) == typeid(Literal)) {
        std::cout << "got a literal\n";
    }
    else if (typeid(*tok) == typeid(Operator)) {
        std::cout << "got an operator\n";
    }
}

答案 2 :(得分:2)

我会问一个不同的问题:你为什么要重新发明轮子而不是使用现有的可能性,即虚拟成员(多态)?如果没有一些强有力的理由这样做,我会称之为不好的做法。

你可以重载虚拟成员,即使你的指针属于基类,你仍然会调用实际(子)类的成员(注意你通常也需要一个虚拟析构函数,但是我'为了简单起见,跳过这个):

class Token {
public:
    virtual void somethingSpecial() {
        std::cout << "Hello!" << std::endl;
    }
}

class Literal : public Token {
public:
    virtual void somethingSpecial() {
        std::cout << "I'm a literal!" << std::endl;
    }
}

class Operator : public Token {
public:
    virtual void somethingSpecial() {
        std::cout << "I'm an operator!" << std::endl;
    }
}

然后,在你的迭代中,你可以做一些简单的事情:

std::vector<Token*> tokens;

tokens.push_back(new Literal());
tokens.push_back(new Operator());
tokens.push_back(new Literal());

for (std::vector<Token*>:iterator a = tokens.begin(); a != tokens.end(); ++a)
    a->somethingSpecial();

结果与您的代码类似。正在运行的实际代码将基于子类中的实际实现。在这种情况下,您最终得到以下输出:

  

我是文字!

     

我是运营商!

     

我是文字!

如果一个子类没有实现虚函数,那么将调用基类的版本(除非你把它作为抽象;然后它就是必须的)。

答案 3 :(得分:1)

你可以这样做:

class CToken {
  public:
    virtual bool isLiteral(void) const = 0;
//...
};

并在两个子类上定义它。

然后,如果你想使用运算符或文字的事实,解决方案是声明一个getValue()函数,但不要使用switch(...)。实际上,如果你想要面向Obect的实践,你应该在CToken中使用虚拟函数,它允许子类自动解释自己,运算符作为运算符,值作为值。

答案 4 :(得分:0)

你应该从不向下投射 - 除非你需要。

一个简单的例子,说明何时适当的是在框架周围传递消息。假设每个消息类型都必须是可序列化的,因此基类接口将提供由衍生类覆盖的虚拟“serialise()”方法,并且消息传递框架将对所有类型的所有消息进行多态处理。

然而,这些消息可能代表什么,因此它们拥有的属性和行为可能大不相同 - 从GPS坐标到电子邮件到星历数据的任何事情都是在尝试捕获基类接口中的所有多样性的情况下没什么感觉。一旦代理收到消息,它知道它感兴趣(根据消息类型)然后(并且只有那时)向下转换到实际的正确类型才能访问有意义的消息内容是合适的 - 因为你已经达到了消息不能以纯粹抽象的方式对待。

向下倾斜本身并不是一种失败。使用RTTI的成本远高于添加一个简单的枚举字段,并且经常推荐的“保持动态投射直到某些东西起作用”应该受到比我想象中发明的更严重的惩罚!