The answer to this question depends on the lifetime policy you are trying to achieve.
If you want reference counting so resources are deallocated when the last handle goes out of scope then shared_ptr and intrusive_ptr are the only options that I'm aware of. You could handroll something, but I'd think long and hard before doing that. If your program is single threaded then the easiest thing to do may be to use Boost shared pointers with the atomic counting disabled:
#define BOOST_SP_DISABLE_THREADS
#include <boost/shared_ptr.hpp>
That should reduce the cost significantly. Of course the comment above is correct that before going this route you would normally profile the code to make sure it really is a bottleneck, but that discussion is a bit of a digression from the question you asked.
If, instead of reference counting, you are OK with the resources being persistent you could use a static cache:
// In the header.
#include <memory>
#include <functional>
namespace detail {
using deleter_t = std::function<void (void *)>;
using handle_t = std::unique_ptr<void, deleter_t>;
void *search_persistent_cache(std::string const &id);
void add_to_persistent_cache(std::string const &id, void *);
}
template<typename T, typename... Args>
T &get_persistent_resource(std::string const &id, Args&&... args) {
auto resource = search_persistent_cache(id);
if(resource) {
return *static_cast<T*>(resource);
}
auto uniq_instance = std::unique_ptr<T>(std::forward<Args>(args)...);
auto raw_instance = uniq_instance.get();
detail::deleter_t deleter = [](void *resource) -> void {
delete static_cast<T*>(resource);
};
try {
add_to_persistent_cache(id,
detail::handle_t { uniq_instance.release(), std::move(deleter); });
} catch(...) {
delete instance;
return nullptr;
}
}
That should be enough for the general idea. I will leave it up to you to implement the static storage and undefined functions in a source file. There are some tweaks that you can make, such as if you don't need the resources indexed by an identifier or you want to avoid using void* so debug builds can work using boost::polymorphic_down_cast.
I can't think of any other lifetime management strategies that are not significantly more complex than the ones shown above.