简体   繁体   中英

How can I add a member function to the inherited class that needs it without affecting the other sibling classes all while maintaining Polymorphism?

I want to add Token *get_left() const; to class OPERATOR:public Token{} but for the sake of polymorphism, I need to do this: class Token {virtual Token *get_left() const = 0;} .This is fine, but because class Token {} has other inherited classes the compiler forces me to implement that function to all inherited classes. Is there a way to only have that function in the class that needs it all while maintaining polymorphism?

Token.h

class Token {
protected:
    Type type;
    Token *Next;
    Token(Type type);
public:
    Type get_type() const;
    void set_type(Type type);
    virtual char *get_value() const = 0;
    virtual int  get_len() const = 0;
    virtual Token *next() = 0;
    virtual void set_next(Token *tok_ptr) = 0;
    virtual Token *get_left() const = 0;
    virtual void set_left(Token *tok_ptr) = 0;
};

Operator.h

class OPERATOR:public Token {
private:
    char  *value;
    Token *left, *right;
    int   len;
public:
    OPERATOR(char *value);
    ~OPERATOR();
    char *get_value() const;
    void set_value(char *value);
    int get_len() const;
    void set_len(char *value);
    Token *get_left() const;
    void set_left(Token *tok_ptr);
    Token *get_right() const;
    void set_right(Token *tok_ptr);
    Token *next();
    void set_next(Token *tok_ptr);
};

STRING.h

class STRING: public Token {
private:
    int  len;
    char *value;
public:
    STRING(char *str);
    ~STRING();
    int get_len() const;
    void set_len(char *str);
    char *get_value() const;
    void set_value(char *str);
    Token *next();
    void set_next(Token *tok_ptr);
};

This question needs a little work. We only have your declarations, and it matters how and when "get_left" and "get_right" should be called. It appears you're writing a tokenizing front end for a parser, so as a side note, I'd recommend your looking into yacc (or bison or one of its variants) and lex (or flex or one of its variants). But let's move on to the C++ question. If it only makes sense to talk about "get_left" and "get_right" for a particular subclass, polymorphism as a technique/concept does not require you to have every method you use at every level of your abstraction in the base class. I'm going to simplify your code to illustrate:

enum Type
{
  string_type = 0,
  operator_type,
  ... // the rest
};

class token {
protected:  // stuff all tokens have (I guess)
  Type type; // side-note: if polymorphism is used correctly, a "type" field should not be needed
  std::string value; // they all seem to need value as well, so we put it here
  token *next; // all objects of type "token" need to be able to belong to linked list (let's say)

public:

  token(Type _type, const char* _value)
    : type(_type)
    , value(_value)
    , next(NULL)
  {}

  // if they share the above data, there's no real reason to make these
  // virtual, let alone abstract. If there REALLY is a different way
  // that a string returns its value as opposed to how an operator returns
  // it's value, then I guess you'd want to make a virtual function out
  // of the accessor, but you still may have a default implementation if
  // most tokens simply return the contents of their value string.

  Type get_type() const { return type; }
  void set_type(Type type) { this.type = type; }
  const char* get_value() const { return value.c_str(); }
  std::size_t get_len() const { return value.length(); }
  token* next() const { return next; }

  virtual void process() = 0;
};

Let's stop here. This is where it becomes important to understand more than just the interfaces. The flow of control is just as important as the class definitions to how polymorphism works. There's only one abstract function - process. That's because for simplicity, let's say that there's string pattern scanner out there, identifying tokens, and categorizing them and producing what appears to be a linked list of objects, all of which are based in token, but each of which is an instance of a concrete class. Once this list is finished, we iterate through it, calling the process method on each one, which operates on each appropriately. That's the essence of polymorphism. I don't know what your flow of control actually is, but if it involves get_left and get_right on objects that don't need those operations, you've already "broken polymorphism". So your scanner code is roughly -

1. get next space delimited string
2. use contextual information to decide its type
   a. if type is an operator, create an object of type "operator" with type-specific data.
   b. same for string
   c. same for all other token types.
3. because each of these concrete objects are also instances of the base class
   token, you add each of them to a linked list with a head of type token*. The
   maintenance code for this list (adding, deleting, iterating) only has to
   know that these are all tokens.
4. repeat 1-3 until out of token candidates.
5. iterate through the list of abstract tokens, calling the "process" function.

So now here's your operator class -

class operator_token : public token
{
private:
  // these are unique to "operator_token", but it has all the others from "token"
  token* left_operand;
  token* right_operand;

public:
  // the scanner code will be explicitly creating an instance of "operator_token"
  // with its unique constructor and the contextual information it needs,
  // but the code that adds it to the linked list and the code that iterates
  // through that linked list only knows it's a "token".

  operator_token(const char* value, token* left, token* right)
    : token(operator_type, value)
    , left_operand(left)
    , right_operand(right)
  {}

  virtual void process() override
  {
    // just something silly to illustrate. This implementation of "process"
    // can use left_operand and right_operand, because it will only be called
    // on an "operator_token" sub-class of "token".
    std::cout << "expression found: "
         << left_operand.get_value()
         << " " << value << " "
         << right_operand.get_value()
         << std::end;
  }
};

And the simpler "string_token" sub-class -

class string_token : public token
{
// no private members because (in my simplified example) strings are pretty
// generic in their data needs
public:
  string_token(const char* value)
    : token(string_type, value)
  {}   // just the basics

  virtual void process() override
  {
     std::cout << "string: " << value << std::end;
  }
};

That's polymorphism. The code that calls the "process" method doesn't know the token's type, but each overridden implementation of "process" does, and can use class-specific information in its operation. Hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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