C++ 如何用不同类型(没有模板)覆盖 class 字段?
C++ How to override class field with a different type (without template)?
所以我试图用 C++ 编写一个简单的解释器,但是 运行 遇到了一些问题。我有一个 Token
class,它包含一个枚举 TokenType
和一个 TokenValue
对象。 TokenValue
class 是其他几个 class 的基础 class(TV_String
、TV_Int
和 TV_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) ...
...
所以我试图用 C++ 编写一个简单的解释器,但是 运行 遇到了一些问题。我有一个 Token
class,它包含一个枚举 TokenType
和一个 TokenValue
对象。 TokenValue
class 是其他几个 class 的基础 class(TV_String
、TV_Int
和 TV_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) ...
...