简体   繁体   中英

Detect whether a method has been overridden

Question

Given a C++ base class pointer, is there a way to detect whether a certain virtual method has been overridden?

Background

I'm writing an interpreter for a little language. I have a base class to represent values with specializations for the various value types. Here's a simplified example:

class Value {
  public:
    virtual bool CanBeString() const { return false; }
    virtual std::string GetAsString() const {
      throw std::logic_error("Value cannot be represented as a string.");
    }

    virtual bool CanBeInt() const { return false; }
    virtual int GetAsInt() const {
      throw std::logic_error("Value cannot be represented as an int.");
    }
};

class StringValue : public Value {
  public:
    bool CanBeString() const override { return true; }
    std::string GetAsString() const override { return m_string; }
  private:
    std::string m_string;
};

class IntValue : public Value {
   public:
     // Even though this is an integer, it can be represented as a string.
     bool CanBeString() const override { return true; }
     std::string GetAsString() const override { /* return string rep of m_int */ }

     bool CanBeInt() const override { return true; }
     int GetAsInt() const override { return m_int; }
   private:
     int m_int;
};

The interpreter works with pointers to Value s and does type-checking at runtime. Suppose the interpreter has a value and it needs to execute an operation on it that applies only to ints. It will first check pValue->CanBeInt() . If true, the interpreter proceeds with pValue->GetAsInt() and does the processing. If the value is not compatible with an int, it reports a type error.

The base implementations of GetAsXxx should never be executed. If one is executed, it means there's a bug in the interpreter (it forgot to check the type first). Those throw statements are to signal me to fix the interpreter; I do not want to throw an exception because of a type error in the code that's being interpreted .

Motivation

In order to make it easier (and less error prone) to add new types to the system, I've been trying to determine if there's a way to eliminate the need for the derived classes to override the CanBeXxx by being able to detect if the corresponding GetAsXxx method has been overridden.

What I've Tried

My specific idea was to change the CanBeXxx methods to non-virtual ones defined in the base class that try to compare the member function pointer for the GetAsXxx method to the one for Value::GetAsXxx . As in:

bool CanBeInt() const { return &GetAsInt != &Value::GetAsInt; }

Alas, this doesn't compile because apparently you can't get a member function pointer for a method that's already bound. Is there a variation on this idea or another approach that would allow for this little slice of reflection?

Pulling apart member method pointers to do comparisons is compiler-specific, as different compilers handle method pointers differently. However, there is an alternative design you might consider. It won't completely eliminate the problem, but it will simplify it a little, while still providing flexibility to add more types in the future:

const unsigned int CanBeInt = 1;
const unsigned int CanBeString = 2;
...

class Value {
  private:
    unsigned int flags;
  public:
    Value(unsigned int aflags) : flags(aflags) {}

    unsigned int GetFlags() const { return flags; }

    inline bool CanBeString() const { return (flags & CanBeString); }
    virtual std::string GetAsString() const {
      throw std::logic_error("Value cannot be represented as a string.");
    }

    inline bool CanBeInt() const { return (flags & CanBeInt); }
    virtual int GetAsInt() const {
      throw std::logic_error("Value cannot be represented as an int.");
    }
};

class StringValue : public Value {
  public:
    StringValue() : Value(CanBeString) {}
    std::string GetAsString() const override { return m_string; }
  private:
    std::string m_string;
};

class IntValue : public Value {
   public:
     // Even though this is an integer, it can be represented as a string.
     IntValue() : Value(CanBeInt | CanBeString) {}
     std::string GetAsString() const override { /* return string rep of m_int */ }
     int GetAsInt() const override { return m_int; }
   private:
     int m_int;
};

if (pValue->CanBeInt()) {
    int val = pValue->GetAsInt();
    ...
}


if (pValue->GetFlags() & CanBeInt) {
    int val = pValue->GetAsInt();
    ...
}

if (pValue->CanBeString()) {
   std::string val = pValue->GetAsString();
   ...
}

if (pValue->GetFlags() & CanBeString) {
   std::string val = pValue->GetAsString();
   ...
}

Another option, like @Beta suggested:

class Value {
  public:
    virtual bool GetAsString(std::string &) const { return false; }
    virtual bool GetAsInt(int &) const { return false; }
};

class StringValue : public Value {
  public:
    bool GetAsString(std::string &value) const override { value = m_string; return true; }
  private:
    std::string m_string;
};

class IntValue : public Value {
   public:
     // Even though this is an integer, it can be represented as a string.
     bool GetAsString(std::string &value) const override { value = ...; return true; }
     bool GetAsInt(int &value) const override { value = m_int; return true; }
   private:
     int m_int;
};

std::string val;
if (!pValue->GetAsString(val))
   throw std::logic_error("Value cannot be represented as a string.");

int val;
if (!pValue->GetAsInt(val))
   throw std::logic_error("Value cannot be represented as a int.");

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