避免空指针

时间:2016-10-10 13:16:48

标签: c++ c++11 void-pointers

我在C ++ 11中实现自己的编程语言。

我设计的一种数据类型是Token类。它旨在存储从源文件读取的令牌,包括令牌的内容,它的类型以及遇到它的行。

令牌可以是单字符符号,长度字符串,数字或名称。所以它需要能够存储不同的数据类型,可以是符号字符,数字加倍,名称和字符串的std ::字符串。

我实现这个的方法是将该值存储在void指针中,并添加一个custome枚举的属性,这有助于理解你应该将该void指针强制转换为什么类型。

当然我可以为Token的每个子类型创建一个类,但我在某种程度上需要将它们全部存储为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指针和(可能)枚举?每个人都在告诉我这个设计是错的,但我没有提出如何真正改善这种情况的建议,所以我在这里问。

2 个答案:

答案 0 :(得分:2)

  

令牌可以是单字符符号,长度字符串,数字或名称。

每当你有一个可以成为众多事物之一的物体时,那就是一个总和类型/不相交的联合/变体。这直接映射到:

using Token = variant<char, std::string, int, Name>;

(其中variant此处为boost::variant或C ++ 1z中的新内容std::variant)。这是一个类模板,它在内部跟踪它是哪种类型,并以类型安全的方式向您公开。

  

我实现这个的方法是将该值存储在void指针中,并添加一个custome枚举的属性,这有助于理解你应该将该void指针强制转换为什么类型。

variant视为基本上这样做 - 除了void*之外,variant值类型。它是可复制的,可移动的,可分配的,价值可破坏的。它拥有自己的数据。如果您阅读了Boost教程,它将解释如何以类型安全的方式访问底层存储。

答案 1 :(得分:1)

您可以按以下方式删除类型:

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();
}