2

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(). 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. Is there another way than casting to subclass to retrieve that sub class member value ?

Virus721
  • 8,061
  • 12
  • 67
  • 123
  • 2
    It generally suggests you have a flawed design if you need to downcast at all. However, C++ has a built-in way for you to do this, using `dynamic_cast`. – Joseph Mansfield Feb 20 '14 at 10:01
  • Do you have Run-Time Type Information (RTTI) available? If so why not make use of the Polymorphism mechanism. – Simon Bosley Feb 20 '14 at 10:03

5 Answers5

8

A type-field really defeats the object orientated nature of C++. Normally you can solve this with polymorphism:

In CToken define a function virtual doSomething(), with appropriate arguments and return type.

Implement that function in COperatorToken and CLiteralToken.

The runtime will call the appropriate function if you then use aTokens[ nI ]->doSomething();

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
3

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.

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";
    }
}
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • Thanks for your answer. I don't know what typeid() is, but i do not wish to use C++11 features (if this is C++11). – Virus721 Feb 20 '14 at 10:16
  • @Virus721 `typeid` has been around since at least C++98. See [here](http://en.cppreference.com/w/cpp/language/typeid). – Fred Foo Feb 20 '14 at 10:45
2

I'd ask a different question: Why are you trying to reinvent the wheel rather than using existing possibilities, i.e. 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).

Community
  • 1
  • 1
Mario
  • 35,726
  • 5
  • 62
  • 78
  • Thanks for your answer ! I've edited my question, what do you think please ? :) – Virus721 Feb 20 '14 at 10:14
  • Why would you want to know which operator you've got? Part of the whole idea is that the "outside world" doesn't have to know anything about the implementation. So you'd probably just add a function `process()`, that would then look at elements on your stack or wheterever to get the actual literals to use. – Mario Feb 20 '14 at 10:16
1

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. 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.

Alex
  • 9
  • 4
  • While this is true, why even bothering with such an virtual member rather than just making the actual call a virtual member? – Mario Feb 20 '14 at 10:07
  • Thanks for your answer ! I've edited my question, what do you think please ? :) – Virus721 Feb 20 '14 at 10:14
  • Because it requires the two subclasses to implement this function. And then, the main object CToken is a interface, so anything using a CToken-type object could ask if it is a literal or not. – Alex Feb 20 '14 at 10:17
0

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.

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. 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!

Grimm The Opiner
  • 1,778
  • 11
  • 29
  • Actually the only reason why CToken class exist is to allow me to store all these different types of tokens into a single array that i can then analyze. If i encouter an operator token, i want to know it's type, and if i encouter a literal token , iwant to know the value of that literal. I parse these token lists to create trees (representing expressions) out of them with operators as nodes, and literals / variables as leaves. I need to access the subclasses data to create these tree nodes and leaves. – Virus721 Feb 20 '14 at 11:00
  • *My* example wasn't meant to be *your* example, just *an* example. ;-) But the same arguments apply, if your tokens need to be abstracted for certain cases but can in their specifics have very different attributes and behviours, then so on and so forth. – Grimm The Opiner Feb 20 '14 at 11:21