我需要一个可搜索的GUID集合(存储为16个字节),其中实际的唯一ID是智能指针结构/类的成员。这是引用计数并由“最后引用删除”基础上的其他对象指向 - 类似于std::shared_ptr
。但由于我的智能指针类的自定义性质,我不想使用shared_ptr
。
但是,我确实希望使用std::unordered_map
或std::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中的一些解除引用来实现,但我不确定如何。
答案 0 :(得分:5)
使用标准哈希容器,你需要一个可哈希的密钥(即,可以通过散列算法转换为size_t的东西)和密钥的等价运算符,以防哈希冲突(即两个GUID不同)但是哈希到相同的值。)
为了通过GUID查找SmartPointer,您可能需要unordered_map而不是unordered_set。请参见底部的示例。
在任何情况下,有两种方法可以散列自定义类:(1)specialize std :: hash或(2)在散列容器的类型定义中提供散列和可能的等式函子。
为你的智能指针专门化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 );
}
根据经验,如果您将原始指针存储在标准容器中,那么您可能做错了。如果您正在存储智能指针的原始指针,那么双重如此。引用计数指针的点是所包含的指针(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对象的实现,以便在不影响用户代码的情况下进行内部重新计数。