I have a situation where I'd like to wrap up some plain C handles into a class, with a destructor and other niceties, in C++. I would like the wrapper class to have the exact same size as the handle, so I can pass an array of the wrappers into the C api without having to allocate and translate them first.
The handle is not unique - it has some extra information, which is known at creation time - so I can't use the handle itself as a key in one big map. Instead, I'm trying to use the address of the handle as the key.
Details aren't really important on this one - it has data, savvy?
class InstanceMetadata { ... };
The header for the wrapper class:
class Instance
{
public:
Instance(const Instance&) = delete;
Instance(Instance&& other);
Instance(InstanceHandle handle, InstanceMetadata metadata);
~Instance();
Instance& operator=(Instance&) = delete;
Instance& operator=(Instance&& other);
InstanceHandle GetHandle() const;
void ResetHandle();
private:
InstanceHandle handle;
};
A thin wrapper around std::map, which handles synchronization and nullptrs
template<typename Handle, typename Metadata>
class MetadataCache
{
public:
void Set(Handle& key, Metadata* value)
{
std::lock_guard<std::mutex> guard(mutex);
data[&key] = value;
}
Metadata* Get(Handle& key)
{
std::lock_guard<std::mutex> guard(mutex);
std::map<Handle*, Metadata*>::iterator it = data.find(&key);
if (it == data.end())
return nullptr;
return it->second;
}
void Erase(Handle& key)
{
std::lock_guard<std::mutex> guard(mutex);
std::map<Handle*, Metadata*>::iterator it = data.find(&key);
if (it != data.end())
data.erase(it);
}
private:
std::mutex mutex;
std::map<Handle*, Metadata*> data;
};
The implementation for the wrapper class:
using namespace std;
using namespace vulkan;
MetadataCache<InstanceHandle, InstanceMetadata> cache;
Instance::Instance(Instance&& other) :
handle(other.handle)
{
other.handle = InstanceHandle();
cache.Set(this->handle, cache.Get(other.handle));
cache.Erase(other.handle);
}
Instance::Instance(InstanceHandle handle, InstanceMetadata metadata) :
handle(handle)
{
cache.Set(handle, new InstanceMetadata(metadata));
}
Instance::~Instance()
{
delete cache.Get(handle);
if (handle)
vkDestroyInstance(handle, nullptr);
}
Instance& Instance::operator=(Instance&& other)
{
delete cache.Get(handle);
cache.Set(handle, cache.Get(other.handle));
cache.Erase(other.handle);
other.handle = InstanceHandle();
return *this;
}
InstanceHandle Instance::GetHandle() const
{
return handle;
}
void Instance::ResetHandle()
{
delete cache.Get(handle);
handle = InstanceHandle();
}
The default constructor for InstanceHandle creates a null handle.
I'm still fairly new to move-semantics, so the main things I would like to know are:
- Is it safe to do this? (i.e. have I missed an operator or something?)
- Is there another way to do this?
- Is there any good reason not to do this?
Keep in mind I don't really care about performance, as these objects will not be created or destroyed very often.
auto
for those longmap::iterator
declarations? ;) \$\endgroup\$unordered_map
tomorrow. \$\endgroup\$