C++ 如何用不同类型(没有模板)覆盖 class 字段?

C++ How to override class field with a different type (without template)?

所以我试图用 C++ 编写一个简单的解释器,但是 运行 遇到了一些问题。我有一个 Token class,它包含一个枚举 TokenType 和一个 TokenValue 对象。 TokenValue class 是其他几个 class 的基础 class(TV_StringTV_IntTV_Float)。

这是 TokenValue 及其子 classes 的代码:

// TokenValue.h

class TokenValue
{
public:
    void* value = NULL;

    virtual bool operator ==(const TokenValue& tv) const
    {
        return typeid(this) == typeid(tv) && value == tv.value;
    }
};

class TV_Empty : public TokenValue {};

class TV_String : public TokenValue
{
public:
    std::string value;

    TV_String(std::string value); // The constructors just assign the value argument to the value field
};

class TV_Int : public TokenValue
{
public:
    int value;

    TV_Int(int value);
};

class TV_Float : public TokenValue
{
public:
    float value;

    TV_Float(float value);
};

这是 Token 的代码:

// Token.h

class Token
{
public:
    enum class TokenType
    {
        // all the different types
    }

    TokenType type;
    TokenValue value;

    Token(TokenType type, TokenValue value); // just initialises type and value, nothing else
}

我遇到的问题是,当我使用任何子 classes 时,value 字段没有被更改(当我打印它时它总是显示 00000000,我认为这是void* value = NULL 的值,但不确定)。从研究我认为它可以通过使用模板来解决,但在我的情况下我不能使用模板,因为 Token 永远不知道其对应的类型 TokenValue.

那么我如何覆盖 value 字段的类型和值并在子 classes 和 == 运算符中访问正确的 value

(感谢 Jarod42,我意识到它不会“覆盖”该字段,它会创建一个具有不同类型和相同名称的新字段。)

这种情况有点糟糕,但可以通过一些间接的方式来处理:

struct TokenValue {
    virtual bool equals(const TokenValue&) const = 0;
};

bool operator==(const TokenValue& lhs, const TokenValue& rhs) {
    return typeid(lhs) == typeid(rhs) && lhs.equals(rhs);
}

现在,派生 类 可以拥有自己的 value 字段(如果合适的话),并覆盖 equals,知道参数将始终是它们自己的类型:

struct TV_empty : TokenValue {
    bool equals(const TokenValue&) const { return true; }
};

struct TV_string : TokenValue {
    std::string value;
    bool equals(const TokenValue& other) const {
        return value == static_cast<TV_string&>(other).value;
    }
}

等等。

是的,如果您偏执,可以在 equals 函数中使用 dynamic_cast

您尝试执行的操作将不起作用,因为 TokenValue 是基数 class 并且您正在 按值 将其存储在 Token,因此如果您尝试将 TV_String 对象、TV_Int 对象等分配给 Token::value,您将 slice that object,丢失有关派生 [=37] 的所有信息=] 类型及其数据字段。

要正确使用多态 classes,您需要使 Token::value 字段成为 指向 TokenValue 对象的指针相反,例如:

class TokenValue
{
public:
    virtual ~TokenValue() = default;

    virtual bool equals(const TokenValue*) const = 0;

    bool operator==(const TokenValue &rhs) const {
        return equals(&rhs);
    }
};

class TV_Empty : public TokenValue {
public:
    bool equals(const TokenValue* tv) const override {
        return (dynamic_cast<const TV_Empty*>(tv) != nullptr);
    }
};

class TV_String : public TokenValue
{
public:
    std::string value;

    TV_String(const std::string &value) : value(value) {}

    bool equals(const TokenValue* tv) const override {
        TV_String *s = dynamic_cast<const TV_String*>(tv);
        return (s) && (s->value == value);
    }
};

class TV_Int : public TokenValue
{
public:
    int value;

    TV_Int(int value) : value(value) {}

    bool equals(const TokenValue* tv) const override {
        TV_Int *i = dynamic_cast<const TV_Int*>(tv);
        return (i) && (i->value == value);
    }
};

class TV_Float : public TokenValue
{
public:
    float value;

    TV_Float(float value) : value(value) {}

    bool equals(const TokenValue* tv) const override {
        TV_Float *f = dynamic_cast<const TV_Float*>(tv);
        return (f) && (f->value == value);
    }
};

...
struct EmptyToken {};

class Token
{
public:
    enum class TokenType
    {
        Empty,
        String,
        Int,
        Float
        ...;
    };

    TokenType type;
    std::unique_ptr<TokenValue> value;

    static TokenType GetTokenType(const TokenValue *tv) {
        if (dynamic_cast<TV_Empty*>(tv) != nullptr)
            return TokenType::Empty;
        if (dynamic_cast<TV_String*>(tv) != nullptr)
            return TokenType::String;
        if (dynamic_cast<TV_Int*>(tv) != nullptr)
            return TokenType::Int;
        if (dynamic_cast<TV_Float*>(tv) != nullptr)
            return TokenType::Float;
        return ...;
    }

    Token(std::unique_ptr<TokenValue> value) : Token(GetTokenType(value.get()), std::move(value)) {}

    Token(TokenType type, std::unique_ptr<TokenValue> value) : type(type), value(std::move(value)) {}

    explicit Token(const EmptyToken &) : type(TokenValue::Empty), value(std::make_unique<TV_Empty>()) {}
    explicit Token(const std::string &value) : type(TokenValue::String), value(std::make_unique<TV_String>(value)) {}
    explicit Token(int value) : type(TokenValue::Int), value(std::make_unique<TV_Int>(value)) {}
    explicit Token(float value) : type(TokenValue::Float), value(std::make_unique<TV_Float>(value)) {}
    ...
};
Token tk1(std::string("test"));
Token tk2(12345);
if (*(tk1.value) == *(tk2.value)) ...
if (tk1.value->equals(tk2.value.get())) ...
...

但是,您实际上在做的是复制 std::variant 已经存在的内容(标记联合),因此您应该完全摆脱 TokenValue,而只使用 std::variant,例如:

struct EmptyToken {};

class Token
{
public:
    enum class TokenType
    {
        Empty,
        String,
        Int,
        Float
        ...;
    };

    std::variant<EmptyToken, std::string, int, float, ...> value;

    explicit Token(const EmptyToken &value) : value(value) {}
    explicit Token(const std::string &value) : value(value) {}
    explicit Token(int value) : value(value) {}
    explicit Token(float value) : value(value) {}
    ...

    TokenType GetTokenType() const
    {
        static const TokenType types[] = {TokenType::Empty, TokenType::String, TokenType::Int, TokenType::Float, ...};
        return types[value.index()];
    };

    ...
};
Token tk1(std::string("test"));
Token tk2(12345);
if (tk1.value == tk2.value) ...
...