Умные указатели и unordered_map, unordered_set и т. Д.

Мне требуется коллекция GUID с возможностью поиска (хранящаяся в 16 байтах), где фактический уникальный идентификатор является членом структуры / класса интеллектуального указателя. Эта ссылка подсчитывается и указывается другими объектами на основе «последней ссылки удаляется» — сродни 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);

Я думаю, что это должно быть возможно с некоторой разыменовкой в ​​пользовательских функциях хэш / равенства, таких как Вот но я не уверен, как именно.

2

Решение

В стандартных контейнерах хеширования вам нужен хешируемый ключ (то есть что-то, что может быть преобразовано в size_t с помощью алгоритма хеширования) и оператор эквивалентности для ключей, в случае, если хэши сталкиваются (т. Е. Два идентификатора GUID различны, но хеш для одинаковое значение).

Для поиска SmartPointer по GUID, вы, вероятно, хотите unordered_map, а не unordered_set. Смотрите пример внизу.

В любом случае, существует два способа хэширования пользовательского класса: (1) специализация 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 в качестве своего ключа, так как вы бы использовали при использовании только std :: unordered_set, тогда вы могли бы хэшировать sp.guid непосредственно в первой специализации, вместо того, чтобы использовать его для перехода ко второй специализации.)

После определения этих специализаций он будет автоматически хешироваться для вас в стандартных контейнерах на основе хеша, при условии, что у вас есть сравнения на равенство, определенные для вашего хеш-типа. Используйте это как: std::unordered_map<GUID, SharedPointer> или же std::unordered_set<SharedPointer>, (Подробнее о специализации на этом пути см. Как расширить std :: tr1 :: hash для пользовательских типов?.)

Используйте пользовательские функторы в типе хеш-контейнера

Для (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 >,

Если вы хотите иметь один GUID для всех экземпляров вашего SmartPointer, вы можете захотеть, чтобы 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, несмотря на то, что это ключ к карте и элемент значения на карте, но будет несколько копий его устройства подсчета ссылок.

В 64-битной системе счет может быть 32-битным, а указатель — 64-битным, что означает 12 байт для каждой копии объекта GUID и экономию в 4 байта на фактический GUID. С 32-разрядным счетчиком и указателем, он будет сохранять 8 байтов на GUID, а с 64-разрядным счетчиком и указателем он будет занимать то же пространство, что и данные GUID. Один из первых двух может или не может стоить этого в вашем приложении / платформе, но последний вряд ли стоит.

Если бы это был я, я бы просто сделал копию объекта GUID в качестве ключа, пока не узнал, что это недопустимо на основании измерений. Тогда я мог бы оптимизировать реализацию объекта GUID для внутреннего пересчета, не затрагивая код пользователя.

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]