Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singleton mixin C++

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

like image 395
user Avatar asked May 15 '26 20:05

user


2 Answers

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>
{
};
like image 115
Arpegius Avatar answered May 17 '26 09:05

Arpegius


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:

  • Declare 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>)
  • Make the constructor of Registry protected, rather than private (so constructors of the derived classes can use it implicitly)
  • Remove the get_instance method from the Registry<T> class definition

And 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.

like image 29
jogojapan Avatar answered May 17 '26 10:05

jogojapan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!