简体   繁体   中英

using shared_ptr with a generic registry or shared object storage…or not

Disclaimer; I know this isn't pretty...

So, I'd like to have a registry or shared object storage for global, shared, instances of various different classes (sub systems, managers etc.) I don't want to use singletons for this and I'd like to be able to have a simple registry where I can register any instance against a key and then obtain a shared_ptr to that instance later on.

Through the use of dubious casting, raw-byte views, and underhand coercion I have managed to create a class that does this, and which works (with VS 2015 at least).

I can do things like

myRegistry->add<FooType>(1,2,3);
....
auto shared_foo_ptr = myRegistry->get<FooType>();

which is lovely, but of course the code under the hood smells.

Or does it?

Here it is, I'd really like some comments and constructive tearing-to-shreds;

 class Registry {

    struct reg_shared_ptr_item {
        // raw byte storage for a shared_ptr of any type (assuming they are always the same size)
        char _storage[sizeof(std::shared_ptr<reg_shared_ptr_item>)];
        // the deleter will be bound to the correctly typed destructor of the original share_ptr
        std::function<void(reg_shared_ptr_item&)> _deleter;
    };

    /**
    this union is used to get access to the raw bytes of a pointer-to-member 
    since these can't be cast to anything directly
    **/
    union fptr_hasher {
        char _raw[sizeof(&std::shared_ptr<Registry>::use_count)];
        uint64_t _key_bits;
    };

    template<typename T>
    static uint32_t get_key_for_type() {
        // C++ disallows converting a pointer-to-member to a void* or any other normal pointer
        // but we don't really need that, we just need the bits which will be unique since each type 
        // has it's own implementation
        // NOTE: if the compiler for some reason or other chooses to be clever about instantiation and 
        //       it doesn't actually create a new instance for each type then this will cause collisions
        typedef long (std::shared_ptr<T>::*fptr_t)(void) const;
        fptr_hasher hasher;
        // create a pointer-to-member instance over the union so that we get access to the raw bytes
        fptr_t* fptr = ::new(&hasher) fptr_t;
        *fptr = &std::shared_ptr<T>::use_count;
        return (reg_key_t)hasher._key_bits;
    }

public:

    typedef uint32_t reg_key_t;

    Registry() = default;
    ~Registry() {
        // clean up; invoke the type-bound destructors
        for (auto& kv : _reg_map) {         
           kv.second._deleter(kv.second);
        }
    }

    template<typename T, class...Args>
    bool add(Args&&...args) {

        auto key = get_key_for_type<T>();
        auto found = _reg_map.find(key);
        if (found == _reg_map.end()) {
            // first; initialise the raw memory location to be a proper shared_ptr
            reg_shared_ptr_item entry;
            ::new(entry._storage) std::shared_ptr<T>();
            // create-assign a new shared_ptr instance
            auto sas = reinterpret_cast<std::shared_ptr<T>*>(entry._storage);
            *sas = std::make_shared<T>(args...); //< at this point the instance count is 1
            // store off f-pointer to a properly typed destructor           
            entry._deleter = [](reg_shared_ptr_item& item) {
                reinterpret_cast<std::shared_ptr<T>*>(item._storage)->~shared_ptr<T>();
            };
            _reg_map.emplace(key,entry);
            return true;
        }
        return false;
    }

    template<typename T>
    std::shared_ptr<T> get() const {
        auto key = get_key_for_type<T>();
        auto found = _reg_map.find(key);
        if (found != _reg_map.end()) {
            return *(reinterpret_cast<std::shared_ptr<T>*>(const_cast<char*>(found->second._storage)));
        }
        return nullptr;
    }

    template<typename T>
    static reg_key_t getHash() {
        return get_key_for_type<T>();
    }

private:        
    typedef std::map<reg_key_t, reg_shared_ptr_item> reg_map_t;
    reg_map_t _reg_map;
};

You could use std::type_index as the key, and shared_ptr<void> as the type-erased storage. Something along these lines perhaps ( demo ):

class Registry {
    std::map<std::type_index, std::shared_ptr<void> > registry_;
public:
    template<typename T, class...Args>
    bool add(Args&&...args) {
        std::type_index key(typeid(T));
        if (!registry_.count(key)) {
            auto p = std::make_shared<T>(std::forward<Args>(args)...);
            registry_[key] = p;
            return true;
        }
        return false;
    }

    template<typename T>
    std::shared_ptr<T> get() const {
        auto it = registry_.find(typeid(T));
        if (it == registry_.end()) {
            return std::shared_ptr<T>();
        }
        return std::static_pointer_cast<T>(it->second);
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM