1

我有一张地址图,可以让我用对象存储任意数据。基本上,我正在编写的一个库有一个模板化的函数,它最终用对象存储任意数据。

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.

有什么建议么?

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

编辑:我认为我最初没有很好地表达我的问题。重写。

4

5 回答 5

1

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

于 2013-05-22T01:02:34.947 回答
1

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

#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。但是,该地址在之后立即变得无效。

于 2013-05-22T01:35:29.687 回答
1

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

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

我假设某处有一个get-style 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_ptr协作std::shared_ptr并且std::weak_ptr可以检测何时不再有std::shared_ptr对相关对象的引用。一旦出现这种情况,我们就可以从myMap. 我正在使用myMapregisterObject和我的两个接口getRegisteredData来调用Internal_RemoveDeadObjects以执行清理工作。

myMap是的,每次注册新对象或请求注册数据时,这都会遍历整个过程。根据您认为合适的方式进行修改或尝试不同的设计。

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

于 2013-05-22T05:51:03.837 回答
0

你可以做

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;
}
于 2013-05-22T00:42:42.003 回答
0

这听起来像是…… boost::intrusivehttp://www.boost.org/doc/libs/1_53_0/doc/html/intrusive.html)的工作!我不认为当前的界面会完全按照它的样子工作。稍后有机会我会尝试解决更多细节。

于 2013-05-22T00:51:06.900 回答