首页 > 解决方案 > C++ 在一张地图中存储不同的指针类型(并处理销毁)

问题描述

在我的服务器项目中,我有一个连接类,它处理一个与一个客户端的连接。在这个连接类中,我想为连接类中未定义的不同系统存储不同的数据——因为外部应该完全控制存储的数据。因此,例如,如果我想添加一个小游戏,我可以将小游戏的数据(如 TMinigameData)添加到连接中并使用它,而无需为这个小游戏更改连接中的任何内容。

我目前的方法如下:

    public:
        template <typename T>
        void clear_data()
        {
            auto it = _bind_data.find(typeid(T).hash_code());
            if (it != _bind_data.end())
            {
#ifdef _DEBUG
                delete it->second.second;
#else
                delete it->second;
#endif
                _bind_data.erase(it);
            }
        }

        template <typename T>
        void bind_data(T*&& data)
        {
            bind_data(std::unique_ptr<T>(data));
        }

        template <typename T>
        void bind_data(std::unique_ptr<T>&& data)
        {
            clear_data<T>();

#ifdef _DEBUG
            _bind_data[typeid(T).hash_code()] = std::make_pair(sizeof(T), data.release());
#else
            _bind_data[typeid(T).hash_code()] = data.release();
#endif
        }

        template <typename T>
        T* get_data(bool create_if_not_exists = false)
        {
            auto it = _bind_data.find(typeid(T).hash_code());
            if (it == _bind_data.end())
            {
                if (create_if_not_exists)
                {
                    auto data_ptr = new T();
                    bind_data(std::unique_ptr<T>(data_ptr));
                    return data_ptr;
                }

                return nullptr;
            }

#ifdef _DEBUG
            assert(sizeof(T) == it->second.first, "Trying to get wrong data type from connection");
            return (T*) it->second.second;
#else
            return (T*) it->second;
#endif
        }

    private:
#ifdef _DEBUG
        std::unordered_map<size_t, std::pair<size_t, void*>> _bind_data;
#else
        std::unordered_map<size_t, void*> _bind_data;
#endif

这里的问题是不会调用不同数据的析构函数,因为它是一个空指针。我在将其添加到我的地图时知道类型,但之后它会丢失。我不知道如何存储特定对象的类型/析构函数......我的方法通常是错误的还是我应该怎么做?

标签: c++templatesgeneric-programming

解决方案


正如Rinat Veliakhmedov在他的回答中已经指出的那样,有效解决方案的关键是虚拟继承或自定义删除器。

但是,您不能直接使用虚拟类,因为您会遭受对象切片或无法在同一映射中使用任意类型的影响。

所以你需要更多的间接级别。为了能够使多态方法起作用,您需要进一步的指针来避免对象切片,例如

std::unordered_map<size_t, std::pair<void*, std::unique_ptr<BaseDeleter>>>
std::unordered_map<size_t, std::unique_ptr<void*, std::unique_ptr<BaseDeleter>>>

在第一种情况下,您现在需要在地图之外实现所有正确的删除,第二种情况根本不起作用,因为std::unique_ptr不能用作自定义删除器。在这两种情况下,你能做的最好的是将整个东西包装在一个单独的类中,例如多态方法:

class DataKeeper
{
    struct Wrapper
    {
        virtual ~Wrapper() { }
    };
    template <typename T>
    struct SpecificWrapper : Wrapper
    {
        SpecificWrapper(T* t) : pointer(t) { }
        std::unique_ptr<T> pointer;
    };
    std::unique_ptr<Wrapper> data;

public:
    DataKeeper()
    { }
    template <typename T>
    DataKeeper(T* t)
        : data(new SpecificWrapper<T>(t))
    { }
    template <typename T>
    DataKeeper(std::unique_ptr<T>&& t)
        : DataKeeper(t.release())
    { }
};

现在我们有了一个易于使用的类DataKeeper,它隐藏了所有多态性的东西。我个人认为自定义删除器方法更加整洁;为此,我们将受益于普通函数也可以用作自定义删除器这一事实:

class DataKeeper
{
    template <typename T>
    static void doDelete(void* t)
    {
        delete static_cast<T*>(t);
    }

    std::unique_ptr<void, void(*)(void*)> pointer;
    //                         ^ function pointer type

public:
    DataKeeper()
        : pointer(nullptr, nullptr)
    {}
    template <typename T>
    DataKeeper(T* t)
        : pointer(t, &DataKeeper::doDelete<T>)
        //                       ^ instantiate appropriate template function and pass
        //                         as custom deleter to smart pointer constructor
    { }
    template <typename T>
    DataKeeper(std::unique_ptr<T>&& t)
        : DataKeeper(t.release())
    { }
};

您现在可以尝试如下:

std::unordered_map<size_t, DataKeeper> map;
map[1] = DataKeeper(new int(7));
map.insert(std::pair<size_t, DataKeeper>(2, std::make_unique<double>(10.12)));
map.emplace(3, std::make_unique<std::string>("aconcagua"));

在析构函数中创建一个带有一些输出的类,您会看到它被正确调用。


推荐阅读