知道何时从 std::map<void *, ...中删除关联的用户数据>

Knowing when to delete associated user data from a std::map<void *, ...>

本文关键字:gt 删除 关联 数据 用户 用户数 void 何时 std map lt      更新时间:2023-10-16

我有一个地址映射,允许我用对象存储任意数据。基本上,我正在编写的库具有一个模板化函数,该函数最终使用对象存储任意数据。

std::map<void *, MyUserData>

可以工作,直到传入的对象被销毁,将其用户数据留在映射中。我希望关联的用户数据也被删除,所以我需要以某种方式监听传入对象

的析构函数。

说明问题的一些示例代码:

#include <map>
#include <memory>

struct MyUserData
{
        int someNum;
};
std::map<void *, MyUserData> myMap;
template <typename T>
registerObject<T>(const std::shared_ptr<T> & _object)
{
        static inc = 0;
        myMap[(void *)&_object->get()].someNum = inc++;
}
struct MyObject
{
        int asdf;
};
int main(int _argc, char ** _argv)
{
        auto obj = std::make_shared<MyObject>();
        obj->asdf = 5;
        registerObject(obj);
        obj = 0;
        //The user data is still there.  I want it to be removed at this point.
}

我目前的解决方案是在shared_ptr上设置一个自定义删除器。当对象的析构函数被调用时,这将向我发出信号,并告诉我何时删除相关的用户数据。不幸的是,这需要我的库创建shared_ptr,因为没有"set_deleter"函数。必须在构造函数中初始化。

mylib::make_shared<T>(); //Annoying!

我也可以让用户手动删除他们的对象:

mylib::unregister<T>(); //Equally annoying!

我的目标是能够在没有任何预先注册的情况下惰性地添加对象。

总的来说,我想检测对象何时被删除,并知道何时从std::map中删除对应的对象。

有什么建议吗?

注:我是否应该担心将用户数据留在地图中?对象被分配与先前删除的对象相同地址的可能性有多大?(就我的lib而言,它最终会接收相同的用户数据。)

编辑:我认为我一开始没有很好地表达我的问题。重写。

从您的代码示例中,看起来外部接口是

template <typename T>
registerObject<T>(const std::shared_ptr<T> & _object);

我假设在某处有一个get风格的API。我们称它为getRegisteredData。(可能是内部的)

在这个问题的范围内,我将使用std::weak_ptr<void>而不是void*,因为std::weak_ptr<T>可以判断何时不再有对周围对象的"强引用",但不会通过维护引用来阻止对象被删除。

std::map<std::weak_ptr<void>, MyUserData> myMap;
template <typename T>
registerObject<T>(const std::shared_ptr<T> & _object)
{
    static inc = 0;
    Internal_RemoveDeadObjects();
    myMap[std::weak_ptr<void>(_object)].someNum = inc++;
}
template <typename T>
MyUserData getRegisteredData(const std::shared_ptr<T> & _object)
{
    Internal_RemoveDeadObjects();
    return myMap[std::weak_ptr<void>(_object)];
}
void Internal_RemoveDeadObjects()
{
    auto iter = myMap.cbegin();
    while (iter != myMap.cend())
    {
        auto& weakPtr = (*iter).first; 
        const bool needsRemoval = !(weakPtr.expired());
        if (needsRemoval)
        {
            auto itemToRemove = iter;
            ++iter;
            myMap.erase(itemToRemove);
        }
        else
        {
            ++iter;
        }
    }
}

基本上,std::weak_ptrstd::shared_ptr协作,std::weak_ptr可以检测到何时没有更多的std::shared_ptr引用到相关对象。在这种情况下,我们可以从myMap中删除辅助数据。我使用myMap的两个接口,您的registerObject和我的getRegisteredData作为调用Internal_RemoveDeadObjects执行清理的方便位置。

是的,每次注册一个新对象或请求注册的数据时,这将遍历整个myMap。如果你觉得合适,可以修改,或者尝试不同的设计

你问"我是否应该担心将用户数据留在地图中?"对象被分配的地址与先前删除的对象相同的可能性有多大?"根据我的经验,绝对不是零,所以不要这样做。: -)

我将添加一个注销方法,并让用户注销他们的对象。使用给定的接口,在剥离类型的地方,我看不到检查reff计数的方法,并且c++没有提供检查内存是否已删除的方法。

我想了一会儿,这是我得到的:

#include <memory>
#include <map>
#include <iostream>
#include <cassert>
using namespace std;
struct MyUserData
{
    int someNum;
};
map<void *, MyUserData> myMap;
template<class T>
class my_shared_ptr : public shared_ptr<T>
{
public:
    my_shared_ptr() { }
    my_shared_ptr(const shared_ptr<T>& s)  : shared_ptr<T>(s) { }
    my_shared_ptr(T* t) : shared_ptr<T>(t) {  }
    ~my_shared_ptr()
    { 
        if (unique()) 
        {           
            myMap.erase(get());
        }
    }
};

template <typename T>
void registerObject(const my_shared_ptr<T> & _object)
{
    static int inc = 0;
    myMap[(void *)_object.get()].someNum = inc++;
}
struct MyObject
{
    int asdf;
};
int main() 
{   
    {   
        my_shared_ptr<MyObject> obj2;
        {
            my_shared_ptr<MyObject> obj = make_shared<MyObject>();
            obj->asdf = 5;
            registerObject(obj);
            obj2 = obj;
            assert(myMap.size() == 1); 
        }
        /* obj is destroyed, but obj2 still points to the data */
        assert(myMap.size() == 1);
    }
    /* obj2 is destroyed, nobody points to the data */
    assert(myMap.size() == 0);
}
但是请注意,如果您编写obj = nullptr;obj.reset(),它将不起作用,因为在这些情况下对象不会被销毁(没有调用析构函数)。另外,你不能在这个解决方案中使用auto。

另外,要注意不要像刚才那样调用(void *)&_object.get()。如果我没有大错特错的话,那句话实际上是在获取_object.get()返回的临时对象的地址,并将其强制转换为void。

这听起来像是…boost::intrusive (http://www.boost.org/doc/libs/1_53_0/doc/html/intrusive.html) !我不认为目前的界面会完全像现在这样工作。当我有机会的时候,我会试着解决更多的细节。

你可以直接做

map.erase(map.find(obj));
delete obj;
obj = 0;

这将调用用户数据的析构函数,并将其从映射中删除。

或者你可以创建自己的经理:

class Pointer;
extern std::map<Pointer,UserData> data;
class Pointer
{
private:
    void * pointer;
public:
    //operator ()
    void * operator()()
    {
        return pointer;
    }
    //operator =
    Pointer& operator= (void * ptr)
    {
        if(ptr == 0)
        {
            data.erase(data.find(pointer));
            pointer = 0;
        }
        else
            pointer = ptr;
        return *this;
    }
    Pointer(void * ptr)
    {
        pointer = ptr;
    }
    Pointer()
    {
        pointer = 0;
    }
    ~Pointer(){}
};
struct UserData
{
    static int whatever;
    UserData(){}
};
std::map<Pointer,UserData> data;
int main()
{
    data[Pointer(new UserData())].whatever++;
    data[Pointer(new UserData())].whatever++;
    data[Pointer(new UserData())].whatever++;
    data[Pointer(new UserData())].whatever++;
    Pointer x(new UserData());
    data[x].whatever;
    x = 0;
    return 0;
}