问题
I am implementing my own programming language in C++11.
One of the datatypes I've designed is the Token
class. It is meant to store a token read from the source file, with the content of the token, it's type, and the line at which it was encountered.
A token can be either a single-character symbol, a lenghtful string, a number, or a name. So it needs to be able to store different data types, either a character for symbols, a double for numbers, and a std::string for names and strings.
The way I'm implementing this is by storing that value in a void pointer, and by adding an attribute of a custome enum which helps understand what type should you cast that void pointer to.
Of course I could have a class for each of those subtypes of Token, but I would at some point anyway need to store all of them as a Token*
, which means I will still need to have an enum which helps me know what type should I cast that Token*
to.
Here's the actual code for it:
enum token_type {
symbol,
number,
name,
string
};
struct Token {
void* value = nullptr;
token_type type;
unsigned int line;
Token(void* new_value, token_type new_type, unsigned int new_line):
value(new_value), type(new_type), line(new_line)
{}
~Token() {
switch (type) {
case symbol:
delete (char*) value;
break;
case number:
delete (double*) value;
break;
case name:
case string:
delete (std::string*) value;
}
}
};
What is a good design pattern to implement this which avoids the use of void pointers and (possibly) enums? Everyone keeps telling me that this design is wrong, but I got no suggestions on how to actually improve this situation, so I asked here.
回答1:
You can erase the type as it follows:
class Token {
using Deleter = void(void*);
using Func = void(*)(void*);
template<typename T>
static void proto(void *ptr) {
T t = static_cast<T*>(ptr);
// do whatever you want here...
// ... or use specializations.
}
public:
template<typename T>
Token(T* value):
value{value, [](void *ptr) { delete static_cast<T*>(ptr); }},
func{&proto<T>}
{}
void operator()() {
func(value.get());
}
private:
std::unique_ptr<void, Deleter> value;
Func func;
};
The instance will be correctly deleted for the smart pointer knows the type.
In a similar manner, by means of a bunch of specializations of proto
, you can define different operations for the multiple types you want to deal with.
It follows a minimal, working example:
#include <memory>
#include <iostream>
struct A {};
struct B {};
class Token {
using Deleter = void(*)(void*);
using Func = void(*)(void*);
template<typename T>
static void proto(void *ptr);
public:
template<typename T>
Token(T *value):
value{value, [](void *ptr) { delete static_cast<T*>(ptr); }},
func{&proto<T>}
{}
void operator()() {
func(value.get());
}
private:
std::unique_ptr<void, Deleter> value;
Func func;
};
template<>
void Token::proto<A>(void *ptr) {
A *a = static_cast<A*>(ptr);
// use a
(void)a;
std::cout << "A" << std::endl;
}
template<>
void Token::proto<B>(void *ptr) {
B *b = static_cast<B*>(ptr);
// use b
(void)b;
std::cout << "B" << std::endl;
}
int main() {
Token token{new A};
token();
}
回答2:
A token can be either a single-character symbol, a lenghtful string, a number, or a name.
Whenever you have an object that can be one of many things, that's a sum type / disjoint union / variant. That directly maps into:
using Token = variant<char, std::string, int, Name>;
(where variant
here is either boost::variant or, new in C++1z, std::variant). This is a class template that internally keeps track of which type it is and exposes it to you in a type-safe way.
The way I'm implementing this is by storing that value in a void pointer, and by adding an attribute of a custome enum which helps understand what type should you cast that void pointer to.
Think of variant
as doing basically that - except instead of a void*
, a variant
is a value type. It's copyable, moveable, assignable, value destructible. It owns its own data. If you read up on the Boost tutorial, it will explain how you can access the underlying storage in a type safe way.
来源:https://stackoverflow.com/questions/39959231/avoiding-void-pointers