3

摘要:如何在 C++ 中创建单例 mixin?我试图避免复制相同的get_instance()函数、私有构造函数等。但我想不出一种方法来使它成为 mixin,因为静态实例将由从 mixin 继承的所有东西共享。

使每个派生类成为单例很容易,但是有没有办法在不重复代码的情况下做到这一点?非常感谢您的帮助,我很难过。

代码: 我正在编写一个带有Registry类的程序,用于按名称查找对象。

#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();
  }
};

我的Registry班级是单身人士;它必须是一个单例(这样注册的对象就不会消失)。

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;
}

输出:

1
2
3
4

期望的输出:

1
2
3
1

4

5 回答 5

4

这不是一个混合。您需要声明另一个模板参数并提供混合类。

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>
{
};
于 2012-04-19T04:42:28.780 回答
3

Lionbest 所说的听起来很对。这是一个与您的原始设计更相似的相关想法。

您声明一个模板类,其工作方式类似于Registry对象工厂。我叫它RegAccess

template <typename RegType>
class RegAccess
{
public:
  static RegType & get_instance()
  {
    static RegType instance;
    return instance;
  }
};

为了使它工作,你:

  • 声明RegAccess<Registry<T> >一个朋友Registry<T>(为了能够做到这一点,您需要确保它在之前 Registry<T>的某个地方被定义)
  • 使构造函数成为Registry受保护的,而不是私有的(因此派生类的构造函数可以隐式使用它)
  • 从类定义中删除get_instance方法Registry<T>

然后你的主程序变成:

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;
}

当我对此进行测试时,它产生了所需的输出。

于 2012-04-19T04:51:39.273 回答
2

问题是两者DerivedRegistryA并且DerivedRegistryB共享相同name_to_object_ptr

get_instance不属于Registry<T>任何DerivedRegistry,两者DerivedRegistry实际上都是Registry<int>,例如,两者都是相同的类型。所以两者都共享相同的static存储空间。因为静态存储属于一个类而不是一个对象

instance因此,两者都获得了相同类型的副本Registry<T>

您需要在派生类中有一个实例函数,或者以某种方式将每个派生类视为不同的类型。可以通过更改模板参数来更改类型但不改变逻辑来完成一些操作。然而,这将是一个非常糟糕的设计。

您可以删除get_instancefromRegistry

template <typename T>
class Singleton{
  public:
  static T& get_instance(){
    static T& instance;
    return instance;
  }
};

class DerivedRegistryA : public Registry<int>, public Singleton<DerivedRegistryA>{

};

这将是一个通用解决方案,您可以将该Singleton类插入需要单例的所有类

于 2012-04-19T04:35:34.897 回答
2

强制性说明: Aaarrg 单例:x (*)

话虽如此...

第 1 步:创建类型化注册表。

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

第 2 步:创建一个标记的 Singleton 实现。

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

注意:标签(第二个参数)在实现本身中完全没用。这只是一种允许为类似类型的对象创建不同实例(以及单例)的方法。提供默认值会很好。

第三步:享受。

struct TypeA {};
typedef Singleton<Registry<int>, TypeA> RegistryA;

int main() {
    RegistryA::GetMutableInstance().insert("toto", std::make_shared(3));
}

(*)为什么是单例?

老实说,大多数时候单例是无用的。哦,这肯定会让事情看起来更容易。一开始。

一个 Singleton 单手设法累积全局变量的弱点(多线程问题、测试问题等);并通过其唯一性强制(更多测试问题)和通常更冗长的界面(地狱)在顶部添加了另一层垃圾。GetInstance

您至少应该摆脱唯一性强制并只提供一个常规的全局变量(昨天是Velociraptor Awareness Day,请注意它们可能仍然潜伏着)。

当然,最好的方法是只拥有一个常规对象,并通过引用那些需要它的函数/方法/对象来传递它。更容易跟踪,更容易测试。

于 2012-04-19T06:45:45.613 回答
2

这应该有助于解决您的问题。请注意,此单例不是线程安全的。但如果需要,您可以更改它。

有关详细信息,请参阅CRTP 。

#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;
}
于 2012-04-19T09:37:40.900 回答