避免空指针
Avoiding void pointers
我正在用 C++11 实现我自己的编程语言。
我设计的数据类型之一是 Token
class。它旨在存储从源文件中读取的令牌,以及令牌的内容、类型和遇到它的行。
令牌可以是单字符符号、长字符串、数字或名称。所以它需要能够存储不同的数据类型,或者是符号的字符,数字的双精度,以及名称和字符串的 std::string。
我实现它的方法是将该值存储在一个 void 指针中,并添加一个自定义枚举的属性,这有助于理解您应该将该 void 指针转换为什么类型。
当然我可以为 Token 的每个子类型创建一个 class,但无论如何我会在某些时候需要将它们全部存储为 Token*
,这意味着我仍然会需要有一个枚举来帮助我知道我应该将 Token*
转换为什么类型。
这是它的实际代码:
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;
}
}
};
什么是避免使用 void 指针和(可能)枚举的良好设计模式?每个人都一直告诉我这个设计是错误的,但我没有得到任何关于如何真正改善这种情况的建议,所以我在这里问了。
您可以擦除类型如下:
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;
};
智能指针实例将被正确删除知道类型。
类似地,通过proto
的一系列特化,你可以为你想要处理的多种类型定义不同的操作。
它遵循一个最小的工作示例:
#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();
}
A token can be either a single-character symbol, a lenghtful string, a number, or a name.
只要你有一个对象可以是许多事物之一,那就是和类型/不相交联合/变体。直接映射到:
using Token = variant<char, std::string, int, Name>;
(这里的 variant
是 boost::variant
or, new in C++1z, std::variant
)。这是一个 class 模板,可以在内部跟踪它是哪种类型,并以类型安全的方式将其公开给您。
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.
认为 variant
基本上是这样做的 - 除了 void*
之外,variant
是 值类型 。它是可复制的、可移动的、可分配的、价值可破坏的。它拥有自己的数据。如果您阅读了 Boost 教程,它将解释如何以类型安全的方式访问底层存储。
我正在用 C++11 实现我自己的编程语言。
我设计的数据类型之一是 Token
class。它旨在存储从源文件中读取的令牌,以及令牌的内容、类型和遇到它的行。
令牌可以是单字符符号、长字符串、数字或名称。所以它需要能够存储不同的数据类型,或者是符号的字符,数字的双精度,以及名称和字符串的 std::string。
我实现它的方法是将该值存储在一个 void 指针中,并添加一个自定义枚举的属性,这有助于理解您应该将该 void 指针转换为什么类型。
当然我可以为 Token 的每个子类型创建一个 class,但无论如何我会在某些时候需要将它们全部存储为 Token*
,这意味着我仍然会需要有一个枚举来帮助我知道我应该将 Token*
转换为什么类型。
这是它的实际代码:
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;
}
}
};
什么是避免使用 void 指针和(可能)枚举的良好设计模式?每个人都一直告诉我这个设计是错误的,但我没有得到任何关于如何真正改善这种情况的建议,所以我在这里问了。
您可以擦除类型如下:
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;
};
智能指针实例将被正确删除知道类型。
类似地,通过proto
的一系列特化,你可以为你想要处理的多种类型定义不同的操作。
它遵循一个最小的工作示例:
#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();
}
A token can be either a single-character symbol, a lenghtful string, a number, or a name.
只要你有一个对象可以是许多事物之一,那就是和类型/不相交联合/变体。直接映射到:
using Token = variant<char, std::string, int, Name>;
(这里的 variant
是 boost::variant
or, new in C++1z, std::variant
)。这是一个 class 模板,可以在内部跟踪它是哪种类型,并以类型安全的方式将其公开给您。
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.
认为 variant
基本上是这样做的 - 除了 void*
之外,variant
是 值类型 。它是可复制的、可移动的、可分配的、价值可破坏的。它拥有自己的数据。如果您阅读了 Boost 教程,它将解释如何以类型安全的方式访问底层存储。