[英]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()
,带有适当的参数和返回类型。
在COperatorToken
和CLiteralToken
实现该功能。
如果你使用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.