9
\$\begingroup\$

I realised that what I was doing wasn't particularly smart, so I switched to a different approach. This costs more dynamic allocations but has a much simpler implementation:

#pragma once

#include <boost/utility.hpp>
#include <set>
#include <map>

//! A map of types to values.
//!
//! Associative container which allows mapping of types to values of that
//! type.
class TypeMap : boost::noncopyable {
    typedef void (*destruct_func)(void*);
    class TypeMapStaticInstance : boost::noncopyable {
        destruct_func destroy_;
        std::map<TypeMap const*, void*> map_;

      public:
        TypeMapStaticInstance(destruct_func f)
          : destroy_(f) {}

        void*& get(TypeMap const* tm) {
            return map_[tm];
        }

        void const* cget(TypeMap const* tm) const {
            auto element = map_.find(tm);
            if (element != map_.end())
                return element->second;
            return nullptr;
        }

        void remove(TypeMap const* tm) {
            auto element = map_.find(tm);
            destroy_(element->second);
            map_.erase(element);
        }
    };

    template<typename T>
    class TypeMapDetail {
        TypeMapDetail() = delete;

        static void destroy_impl(void* p) {
            delete static_cast<T*>(p);
        }

      public:
        static TypeMapStaticInstance map_;

        static T& get(TypeMap const* p) {
            auto& element = map_.get(p);
            if (!element)
                element = new T();
            return *static_cast<T*>(element);
        }

        static T const* cget(TypeMap const* p) {
            return static_cast<T const*>(map_.cget(p));
        }
    };

    std::set<TypeMapStaticInstance*> members_;
  public:
    //! Retrieve the data associated with the given type.
    template<typename T>
    T& get() {
        members_.insert(&TypeMapDetail<T>::map_);
        return TypeMapDetail<T>::get(this);
    }

    template<typename T>
    T const* cget() const {
        return TypeMapDetail<T>::cget(this);
    }

    ~TypeMap() {
        for (auto m : members_) {
            m->remove(this);
        }
    }
};

template<typename T>
TypeMap::TypeMapStaticInstance TypeMap::TypeMapDetail<T>::map_(TypeMap::TypeMapDetail<T>::destroy_impl);

Comments:

  • One of the reasons I think this approach may be better is that I don't see any undefined behaviour being invoked. Am I correct about this?
  • Now that standard containers can have stateful allocators, would having each contain destruct_func (and probably construct_func) make sense?

Other issues as always welcome.

\$\endgroup\$
  • 4
    \$\begingroup\$ Can you please provide example code to use this in a small program? \$\endgroup\$ – pacmaninbw Oct 19 '19 at 17:56

Browse other questions tagged or ask your own question.