智能指针和unordered_map,unordered_set等

时间:2013-03-20 14:18:58

标签: c++ hash map unordered-map unordered-set

我需要一个可搜索的GUID集合(存储为16个字节),其中实际的唯一ID是智能指针结构/类的成员。这是引用计数并由“最后引用删除”基础上的其他对象指向 - 类似于std::shared_ptr。但由于我的智能指针类的自定义性质,我不想使用shared_ptr

但是,我确实希望使用std::unordered_mapstd::unordered_set(如果它们足够快)来保存指针集合。

即使智能指针地址是唯一的,因此最好用作哈希,我需要表中的可搜索密钥作为GUID;这样我就可以使用find(guid)快速找到正确的智能指针。

很难用文字解释,所以这里有一些代码:

class SmartPointer
{
public:
   GUID guid;
   int refCount; // Incremented/decremented when things point or stop pointing to it.
   void* actualObject; // The actual data attached to the smart pointer.
};

// Generate a unique 128-bit id.
GUID id;

// Create the smart pointer object.
SmartPointer* sp = new SmartPointer();
sp->guid = id;

// Create a set of SmartPointers.
std::unordered_set<SmartPointer*> set;
set.insert(sp);

// Need to find a SmartPointer based on a known GUID and not the pointer itself.
SmartPointer* found = set.find(id);

我认为这应该可以通过自定义散列/相等函数(如here中的一些解除引用来实现,但我不确定如何。

1 个答案:

答案 0 :(得分:5)

使用标准哈希容器,你需要一个可哈希的密钥(即,可以通过散列算法转换为size_t的东西)和密钥的等价运算符,以防哈希冲突(即两个GUID不同)但是哈希到相同的值。)

为了通过GUID查找SmartPointer,您可能需要unordered_map而不是unordered_set。请参见底部的示例。

在任何情况下,有两种方法可以散列自定义类:(1)specialize std :: hash或(2)在散列容器的类型定义中提供散列和可能的等式函子。

专门化std :: hash

为你的智能指针专门化std :: hash,使它看到GUID,做这样的事情:

namespace std 
{ 
    template<>
    struct hash< SmartPointer > 
    {
        size_t operator()( const SmartPointer& sp ) const
        {
            return hash( sp.guid );
        }
    };

    template<>
    struct hash< GUID > 
    {
        size_t operator()( const GUID& id ) const
        {
            return /* Some hash algo to convert your GUID into a size_t */;
        }
    };
}

(这两个特化可以根据您的需要组合。如果您只使用GUID作为散列键,如下面的unordered_map示例,那么您不需要SmartPointer的专业化。如果您只将SmartPointer作为您的哈希值key,就像你只使用std :: unordered_set时那样,你可以直接在第一个特化中散列sp.guid,而不是让它将can踢到第二个特化。)

通过定义这些特化,它将在标准的基于散列的容器中自动散列,假设您为散列类型定义了相等比较。使用它:std::unordered_map<GUID, SharedPointer>std::unordered_set<SharedPointer>。 (有关以这种方式专业的更多信息,请参阅How to extend std::tr1::hash for custom types?。)

在散列容器类型

中使用自定义仿函数

对于(2),您可以更改无序集/地图的类型并提供仿函数作为模板参数:

struct HashSmartPointer
{
    std::size_t operator()( const SmartPointer& sp ) const
    {
        return /* Some hash algo to convert your sp.guid into a size_t */;
    }
};

std::unordered_set< SmartPointer, HashSmartPointer > mySet;

再次假设您为SmartPointer定义了相等的处理冲突(否则,将另一个参数添加到等于仿函数的unordered_set的模板参数上)。

完整示例

这是一个完整的程序,展示了我认为你要求的东西:

#include <vector>
#include <cstdlib>
#include <cstdint>
#include <algorithm>
#include <cassert>
#include <unordered_map>

class GUID // Some goofy class. Yours is probably better
{
public:
   std::vector<uint8_t> _id;

   GUID()
     : _id(16)
   {
      std::generate(_id.begin(),_id.end(), std::rand);
   }

   friend bool operator ==( const GUID& g1, const GUID& g2 )
   {
      return std::equal( g1._id.begin(), g1._id.end(), g2._id.begin() );
   }

   friend bool operator !=( const GUID& g1, const GUID& g2 )
   {
      return !(g1 == g2);
   }
};

class SmartPointer
{
public:
   GUID guid;
   int refCount; // Incremented/decremented when things point or stop pointing to it.
   void* actualObject; // The actual data attached to the smart pointer.

   friend bool operator ==( const SmartPointer& p1, const SmartPointer& p2 )
   {
      // This may not be right for you, but good enough here
      return p1.guid == p2.guid;
   }
};

struct HashGUID
{
    std::size_t operator()( const GUID& guid ) const
    {
        // Do something better than this. As a starting point, see:
        //   http://en.wikipedia.org/wiki/Hash_function#Hash_function_algorithms
        return std::accumulate( guid._id.begin(), guid._id.end(), std::size_t(0) );
    }
};

int main()
{
   // Create the smart pointer object.
   SmartPointer sp1, sp2, sp3;

   assert( sp1.guid != sp2.guid );
   assert( sp1.guid != sp3.guid );
   assert( sp2.guid != sp3.guid );

   // Create a set of SmartPointers.
   std::unordered_map<GUID, SmartPointer, HashGUID> m;
   m[sp1.guid] = sp1;
   m[sp2.guid] = sp2;
   m[sp3.guid] = sp3;

   const GUID guid1 = sp1.guid;    
   const GUID guid2 = sp2.guid;    
   const GUID guid3 = sp3.guid;    

   // Need to find a SmartPointer based on a known GUID and not the pointer itself.
   auto found1 = m.find( guid1 );
   auto found2 = m.find( guid2 );   
   auto found3 = m.find( guid3 );   

   assert( found1 != m.end() );
   assert( found2 != m.end() );
   assert( found3 != m.end() );

   assert( found1->second == sp1 );
   assert( found2->second == sp2 );
   assert( found3->second == sp3 );
}

下面的OP评论更新

根据经验,如果您将原始指针存储在标准容器中,那么您可能做错了。如果您正在存储智能指针的原始指针,那么双重如此。引用计数指针的点是所包含的指针(actualObject)不重复,而可以有许多浮动的智能指针装置的副本,每个副本对应于引用计数的一个增量并且每个引用相同的包含对象。因此,您通常会看到类似std::unordered_set< std::shared_ptr<MyClass>, Hasher, Equality >的内容。

如果您想为SmartPointer的所有实例设置一个GUID,您可能希望将GUID作为重新计算数据的(秘密)部分:

class SmartPointer
{
public:
   int refCount; // Incremented/decremented when things point or stop pointing to it.
   struct
   {
       GUID guid;
       void* actualObject; // The actual data attached to the smart pointer.
   } *refCountedData;
};

将SmartPointer与std :: unordered_set一起使用时,您只有一个GUID副本,但由于所有散列机制都是std :: unordered_set的内部,因此您无权访问散列密钥。要通过GUID在集合中查找,您需要进行手动搜索,否定哈希的优势。

为了获得你想要的东西,我认为你需要定义自己的哈希容器,让你可以更好地控制来自外部的哈希,或者使用像侵入式引用计数的GUID对象,例如:

class GUID
{
public:
    typedef std::vector<std::uint8_t> ID;
    int refCount;
    ID* actualID;
    // ...
};

SmartPointer sp1, sp2;

std::unordered_map< GUID, SmartPointer > m;
m[ sp1.guid ] = sp1;
m[ sp2.guid ] = sp2;

在这种情况下,只存在GUID actualID的一个副本,即使它是地图的关键字和地图中值的成员,但它的引用计数设备将有多个副本。

在64位系统上,计数可以是32位,指针是64位,这意味着每个GUID对象副本总共12个字节,每个实际GUID节省4个字节。使用32位计数器和指针,它将为每个GUID节省8个字节,并且使用64位计数器和指针,它将占用与GUID数据相同的空间。在您的应用程序/平台中,前两个中的一个可能值得也可能不值得,但最后一个不值得。

如果是我,我只是将GUID对象的副本作为密钥,直到我知道根据测量结果是不可接受的。然后,我可以优化GUID对象的实现,以便在不影响用户代码的情况下进行内部重新计数。