繁体   English   中英

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

[英]C++ - Good or bad practice?

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

例如 :

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(),这是没有意义的。 除了转换为子类以检索该子类成员值之外,还有其他方法吗?

类型字段确实破坏了C ++的面向对象特性。 通常你可以用多态来解决这个问题:

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

COperatorTokenCLiteralToken实现该功能。

如果你使用aTokens[ nI ]->doSomething();运行时将调用相应的函数aTokens[ nI ]->doSomething();

听起来像是在尝试模拟代数数据类型。 通常,您不会这样做,而是通过在基类上放置纯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";
    }
}

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

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

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();

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

我是字面意思!

我是运营商!

我是字面意思!

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

你可以这样做:

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

并在两个子类上定义它。

然后,如果你想使用运算符或文字的事实,解决方案是声明一个getValue()函数,但绝不使用switch(...) 事实上,如果你想要去认为Obect导向的做法,你应该creeate在虚拟函数CToken这允许子类自动解释自己,运营商为运营商和值值。

永远不应该失败 - 除非你需要。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM