简体   繁体   English

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

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

I'm facing situations where i'm finding it handy to store the type (as an enum) of an object in the base class to further cast a pointer to that base class into a subclass pointer depending on the value of that type. 我正面临着这样的情况:我发现在基类中存储对象的类型(作为枚举)以进一步将指向该基类的指针转换为子类指针(取决于该类型的值)。

For example : 例如 :

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
};

And then 接着

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;
   }
}

Is it a bad practice ? 这是一种不好的做法吗?

Thank you :) 谢谢 :)

EDIT: 编辑:

Say i'm analyzing my token list. 说我正在分析我的令牌列表。 At some point i'll want to check if the current token is an operator using, as people suggest, a virtual function virtual bool isOperator() . 在某些时候,我想要检查当前令牌是否是一个操作员,正如人们所建议的那样,使用虚拟函数virtual bool isOperator() Now if it IS an operator, i will want to access that sub class specific stuff to find out, for example, which type of operator it is. 现在,如果它是一个运算符,我将要访问该子类特定的东西,以找出,例如,它是哪种类型的运算符。 In that case, what can i do ? 在那种情况下,我该怎么办? I can't add a method getOperatorType() in my base class, that wouldn't make sense. 我无法在我的基类中添加方法getOperatorType(),这是没有意义的。 Is there another way than casting to subclass to retrieve that sub class member value ? 除了转换为子类以检索该子类成员值之外,还有其他方法吗?

A type-field really defeats the object orientated nature of C++. 类型字段确实破坏了C ++的面向对象特性。 Normally you can solve this with polymorphism : 通常你可以用多态来解决这个问题:

In CToken define a function virtual doSomething() , with appropriate arguments and return type. CToken定义一个函数virtual doSomething() ,带有适当的参数和返回类型。

Implement that function in COperatorToken and CLiteralToken . COperatorTokenCLiteralToken实现该功能。

The runtime will call the appropriate function if you then use aTokens[ nI ]->doSomething(); 如果你使用aTokens[ nI ]->doSomething();运行时将调用相应的函数aTokens[ nI ]->doSomething();

Sounds like your trying to emulate an algebraic data type. 听起来像是在尝试模拟代数数据类型。 Usually, you wouldn't do it this way, but by putting a pure virtual function on the base class and implementing the actual behavior in overrides in the derived classes. 通常,您不会这样做,而是通过在基类上放置纯virtual函数并在派生类的覆盖中实现实际行为。

Also, if you do ever need the pattern you propose, the language runtime knows the type so you don't need to store it: 此外,如果您确实需要您提出的模式,语言运行库会知道类型,因此您无需存储它:

#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";
    }
}

I'd ask a different question: Why are you trying to reinvent the wheel rather than using existing possibilities, ie virtual members (polymorphism)? 我会问一个不同的问题:你为什么要重新发明轮子而不是使用现有的可能性,即虚拟成员(多态)? So I'd call it bad practice if there isn't some strong reason for doing it this way. 因此,如果没有一些强有力的理由这样做,我会称之为不好的做法。

You can overload virtual members and even if your pointer is of the base class, you'll still call the member of the actual (sub) class (note that you'll typically want a virtual destructor as well, but I'm skipping this for simplicity): 您可以重载虚拟成员,即使您的指针是基类,您仍然会调用实际(子)类的成员(请注意,您通常也需要一个虚拟析构函数,但我正在跳过此为简单起见):

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;
    }
}

Then, in your iteration, you can do something as simple as this: 然后,在迭代中,您可以执行以下简单操作:

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

The result will be similar to your code. 结果将类似于您的代码。 The actual code being run will be based on the actual implementation in the sub class. 正在运行的实际代码将基于子类中的实际实现。 In this case, you'd end up with the following output: 在这种情况下,您最终得到以下输出:

I'm a literal! 我是字面意思!

I'm an operator! 我是运营商!

I'm a literal! 我是字面意思!

If a sub class doesn't implement the virtual function, the base class' version would be called (unless you make it abstract; then it would be a must-have). 如果一个子类没有实现虚函数,那么将调用基类的版本(除非你把它作为抽象;然后它将是必须的)。

You could simply do: 你可以这样做:

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

and define it on the two subclasses. 并在两个子类上定义它。

And then, if you want to use the fact that's an operator or a literal, the solution is to declare a getValue() function, but NEVER use a switch(...) for that. 然后,如果你想使用运算符或文字的事实,解决方案是声明一个getValue()函数,但绝不使用switch(...) In fact, if you want to go thought the Obect-Oriented practice, you should creeate a virtual function in CToken which allow subclasses to auto-interpret themselves, operators as operators and values as values. 事实上,如果你想要去认为Obect导向的做法,你应该creeate在虚拟函数CToken这允许子类自动解释自己,运营商为运营商和值值。

You should never down cast - except when you need to. 永远不应该失败 - 除非你需要。

An easy example of when it could be appropriate is passing messages around a framework. 一个简单的例子就是在框架周围传递消息。 Say every message type must be serialisable so the base class interface would provide a virtual 'serialise()' method overridden by dervied classes, and all messages of any type are treated polymorphically by the messaging framework. 假设每个消息类型都必须是可序列化的,因此基类接口将提供由被调用的类覆盖的虚拟“serialise()”方法,并且消息传递框架将对所有类型的所有消息进行多态处理。

However, what those messages might represent and so the attributes and behaviours they possess could be wildly different - anything from GPS coordinates to an email to ephemeris data and that being the case trying to capture all that diversity in the base class's interface makes little sense. 然而,这些消息可能代表什么,因此它们拥有的属性和行为可能大不相同 - 从GPS坐标到电子邮件到星历数据的任何事情,以及尝试捕获基类界面中所有多样性的情况都没有多大意义。 Once an agent has received a message it knows it's interested in (according to the message type) then (and only then) downcasting to the actual correct type in order to access meaningful message contents is appropriate - as you have then reached the point where the message cannot be treated in a purely abstract fashion. 一旦代理收到消息,它知道它感兴趣(根据消息类型)然后(并且只有那时)向下转换到实际的正确类型才能访问有意义的消息内容是合适的 - 因为你已经达到了消息不能以纯粹抽象的方式处理。

Downcasting isn't in itself a failure. 向下倾斜本身并不是一种失败。 Using RTTI comes with a cost far higher than adding a simple enum field, and the oft recommended "keep dynamic casting until something works" deserves a far crueller punishment than I have the imagination to invent! 使用RTTI的成本远远高于添加一个简单的枚举字段,并且经常推荐的“保持动态投射直到某些东西起作用”应该比我想象中发明的更加残酷的惩罚!

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

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