Reputation: 7333
Summary: How can I create a singleton mixin in C++? I am trying to avoid copying the same get_instance()
function, the private constructor, etc. But I can't figure out a way to make this a mixin, because the static instance will be shared by everything that inherits from the mixin.
It's easy to make each derived class a singleton, but is there a way to do it without duplicating code? Thank a lot for your help, I'm stumped.
Code:
I am writing a program with a Registry
class for looking up objects by name.
#include <string>
#include <memory>
#include <map>
#include <string>
#include <assert.h>
template <typename T>
class Registry
{
private:
// make private so that the class can't be instantiated and must be used via get_instance
Registry() {}
protected:
std::map<std::string, std::shared_ptr<T> > name_to_object_ptr;
public:
static Registry<T> & get_instance()
{
static Registry<T> instance;
return instance;
}
void register_name(const std::string & name, T*obj_ptr)
{
assert( name_to_object_ptr.count(name) == 0 );
name_to_object_ptr[name] = std::shared_ptr<T>(obj_ptr);
}
const std::shared_ptr<T> & lookup_name(const std::string & name)
{
assert( name_to_object_ptr.count(name) > 0 );
return name_to_object_ptr[name];
}
int size() const
{
return name_to_object_ptr.size();
}
};
My Registry
class is a singleton; it must be a singleton (so that registered objects will not disappear).
class DerivedRegistryA : public Registry<int>
{
};
class DerivedRegistryB : public Registry<int>
{
};
int main()
{
DerivedRegistryA::get_instance().register_name(std::string("one"), new int(1));
std::cout << DerivedRegistryA::get_instance().size() << std::endl;
DerivedRegistryA::get_instance().register_name(std::string("two"), new int(2));
std::cout << DerivedRegistryA::get_instance().size() << std::endl;
DerivedRegistryA::get_instance().register_name(std::string("three"), new int(3));
std::cout << DerivedRegistryA::get_instance().size() << std::endl;
DerivedRegistryB::get_instance().register_name(std::string("four"), new int(4));
std::cout << DerivedRegistryB::get_instance().size() << std::endl;
return 0;
}
Output:
1
2
3
4
Desired output:
1
2
3
1
Upvotes: 3
Views: 1204
Reputation: 276
this should help to solve your problem. Be aware that this singleton is not thread safe. But you can change it if you need to.
See CRTP for more details.
#include <iostream>
#include <map>
#include <string>
// simple singleton
template <class T>
class Singleton {
public:
static T& Instance() { static T instance; return instance; }
protected:
Singleton(){}
};
// your Registry Base
template <class T>
class Registry : public Singleton< Registry<T> > {
friend class Singleton<Registry>;
public:
void register_name( const std::string& name, T value ){ m_data[name] = value; }
const T& lookup_name( const std::string& name ){ return m_data[name]; }
private:
Registry(){}
Registry(const Registry&){} // to prevent copies, you have to use ::Instance()
std::map<std::string, T> m_data;
};
int main(int argc, char *argv[])
{
Registry<int>& instance = Registry<int>::Instance();
instance.register_name("Value1",1);
Registry<int>::Instance().register_name("Value2",2);
int value = instance.lookup_name("Value1");
std::cout << "Value1=" << value << std::endl;
std::cout << "Value2=" << Registry<int>::Instance().lookup_name("Value2") << std::endl;
return 0;
}
Upvotes: 2
Reputation: 300439
Mandatory note: Aaarrg a Singleton :x (*)
That being said...
Step 1: creating a typed registry.
template <typename T>
class Registry {
public:
typedef std::string Key;
typedef std::shared_ptr<T> ItemPtr;
Registry() {}
Registry(Registry const&) = delete;
Registry& operator=(Registry const&) = delete;
ItemPtr find(Key) const;
void insert(Key, ItemPtr);
private:
typedef std::map<Key, ItemPtr> StoreType;
StoreType _store;
}; // class Registry
Step 2: creating a tagged Singleton implementation.
template <typename Object, typename>
class Singleton {
public:
static Object& GetMutableInstance() {
static Object O;
return O;
}
static Object const& GetInstance() { return GetMutableInstance(); }
private:
Singleton() = delete;
}; // class Singleton
Note: the tag (second parameter) is utterly useless in the implementation itself. It is just a way to allow different instances (and thus singletons) to be created for similarly typed objects. It would be fine to provide a default.
Step 3: enjoying.
struct TypeA {};
typedef Singleton<Registry<int>, TypeA> RegistryA;
int main() {
RegistryA::GetMutableInstance().insert("toto", std::make_shared(3));
}
(*) Why a Singleton ?
Honestly, most of the times a Singleton is about useless. Oh it certainly make thing appear easier. At the beginning.
A Singleton single-handly manages to cumulate the weaknesses of global variables (multi-threading issues, testing issues, etc..); and adds another layer of crappiness on top with its uniqueness enforcement (more testing issues) and generally more verbose interface (GetInstance
hell).
You should at least get rid of the uniqueness enforcement and just provide a regular global variable (yesterday was the Velociraptor Awareness Day, beware they might still be lurking).
The best road, of course, would be to have just a regular object, and pass it down by reference to those functions/methods/objects that need it. Easier to track, easier to test.
Upvotes: 2
Reputation: 5887
This is not a mixin. You need to declare another template parameter and provide mixed class.
template <typename T, typename Mixie>
class Registry
{
private:
Registry() {}
protected:
std::map<std::string, boost::shared_ptr<T> > name_to_object_ptr;
public:
static Registry<T,Mixie> & get_instance()
{
static Registry<T,Mixie> instance;
return instance;
}
...
};
class DerivedRegistryA : public Registry<int,DerivedRegistryA>
{
};
class DerivedRegistryB : public Registry<int,DerivedRegistryB>
{
};
Upvotes: 4
Reputation: 12904
The problem is both DerivedRegistryA
and DerivedRegistryB
is sharing the same name_to_object_ptr
get_instance
belongs to Registry<T>
not any DerivedRegistry
and both DerivedRegistry
is actually Registry<int>
e.g. both are same type. so both are sharing the same static
storage. as static storage belongs to a class not to an object.
So both are getting the same copy of instance
which is of Registry<T>
type.
You need to have an instance function in the derived class or somehow treat every derived class as a different type. that some how can be done by making changes in your template arguments that changes the type but doesn't alter your logic. However that will be a very bad design.
you can remove the get_instance
from Registry
and instead
template <typename T>
class Singleton{
public:
static T& get_instance(){
static T& instance;
return instance;
}
};
class DerivedRegistryA : public Registry<int>, public Singleton<DerivedRegistryA>{
};
This would be a generic solution, and you can plug the Singleton
class to all classes where you need singleton
Upvotes: 2
Reputation: 70037
What lionbest said sounds right. Here is a related idea that is more similar to your original design.
You declare a template class that works in a way similar to a factory for Registry
objects. I called it RegAccess
:
template <typename RegType>
class RegAccess
{
public:
static RegType & get_instance()
{
static RegType instance;
return instance;
}
};
To make it work, you:
RegAccess<Registry<T> >
a friend of Registry<T>
(to be able to do that you need to make sure it's defined someplace before Registry<T>
)Registry
protected, rather than private (so constructors of the derived classes can use it implicitly)get_instance
method from the Registry<T>
class definitionAnd then your main program becomes:
int main()
{
RegAccess<DerivedRegistryA>::get_instance().register_name(std::string("one"), new int(1));
std::cout << RegAccess<DerivedRegistryA>::get_instance().size() << std::endl;
RegAccess<DerivedRegistryA>::get_instance().register_name(std::string("two"), new int(2));
std::cout << RegAccess<DerivedRegistryA>::get_instance().size() << std::endl;
RegAccess<DerivedRegistryA>::get_instance().register_name(std::string("three"), new int(3));
std::cout << RegAccess<DerivedRegistryA>::get_instance().size() << std::endl;
RegAccess<DerivedRegistryB>::get_instance().register_name(std::string("four"), new int(4));
std::cout << RegAccess<DerivedRegistryB>::get_instance().size() << std::endl;
return 0;
}
When I tested this, it generated the desired output.
Upvotes: 3