【问题标题】:Avoiding void pointers避免空指针
【发布时间】:2017-02-18 22:21:26
【问题描述】:

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

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

标记可以是单字符符号、长字符串、数字或名称。所以它需要能够存储不同的数据类型,一个字符代表符号,一个双精度代表数字,一个 std::string 代表名称和字符串。

我实现这一点的方式是将该值存储在一个 void 指针中,并添加一个自定义枚举的属性,这有助于了解您应该将该 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 指针和(可能)枚举?每个人都在告诉我这个设计是错误的,但是我没有关于如何真正改善这种情况的建议,所以我在这里问。

【问题讨论】:

  • 你看过boost::variant吗?
  • 嗯,大多数编译器都淹没在 void 指针中,所以我不会说使用它们一定是错误的,但是既然你知道你需要的数据类型,你可以考虑使用变体或类似的数据类型。
  • @Hayt 不确定我能否使用 boost。我正在使用 Linux 模拟器在 Android 上工作,但不存在 boost。也许我应该手动下载它。我仍然不确定它的便携性。
  • 你可以试试。 Boost 有一个它可以构建的编译器的列表。如果你可以访问 C++17,那么你就有 std::variant 了。
  • @Hayt 刚刚尝试过。是的,我可以将 g++ 编译标志用于 c++17 (-std=c++1z)。你能在此基础上给出答案吗?

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


【解决方案1】:

标记可以是单字符符号、长字符串、数字或名称。

只要你有一个对象可以是许多事物之一,那就是求和类型/不相交的联合/变体。直接映射成:

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

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

我实现这一点的方式是将该值存储在一个 void 指针中,并添加一个自定义枚举的属性,这有助于了解应该将该 void 指针转换为什么类型。

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

【讨论】:

    【解决方案2】:

    您可以按如下方式擦除类型:

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

    【讨论】:

    • 哇哇哇?你能添加几个(我的意思是很多)更多的cmets吗?课堂内的using 对我来说是全新的。另外我将如何使用它?
    • @user6245072 using 声明...从未听说过。好吧,很好,我会在几分钟内添加更多细节。
    • 等等,不,我认识他们,只是我从未在课堂上见过他们。也是value{value)搞错了吗?
    • 这是一个类型擦除的空函数对象。我不确定这如何解决 OP 的令牌问题,令牌可以是 4 种类型之一。
    • @user6245072 在示例中忽略一些警告。 :-) ...如果您使用变量,您可以安全地删除这些行。
    猜你喜欢
    • 2011-10-01
    • 2021-04-25
    • 2019-04-24
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-20
    • 2017-03-21
    相关资源
    最近更新 更多