单例模式 in Modern C++

文章目录

  • 单例模式 - Singleton
    • 懒汉模式 && 饿汉模式
    • 线程安全单例 && 每线程单例
    • 控制反转(Inversion of Control,IoC)容器实现单例
    • 单态模式(Monostate)

单例模式 - Singleton

Design Patterns in Modern C++: The Singleton is the most hated design pattern in the (rather limited) history of design patterns. Just stating that, however, doesn’t mean you shouldn’t use the singleton: a toilet brush is not the most pleasant device either, but sometimes it’s simply necessary

《全唐诗》:夫物有所用,用之各有宜

懒汉模式 && 饿汉模式

懒汉模式:时间换空间,只有当第一次使用到类的实例时,才会初始化

懒汉模式所需解决的最主要问题是 线程安全

其原因在于,当有多个线程同时执行至 new LazySingleton() 所在的程序段时,instance 实例可能会被多次 new

class LazySingleton {
private:
    static LazySingleton *instance;
    LazySingleton() {};

public:
    static auto GetInstance() -> LazySingleton * {
        if (instance == nullptr) {
            // new 新实例的时候,线程不安全
            instance = new LazySingleton();
        }
        return instance;
    };
};

LazySingleton* LazySingleton::instance = nullptr;

饿汉模式:空间换时间,加载进程时完成静态成员创建,

创建好的实例可能程序自始至终都未使用过,会造成资源浪费

class EagerSingleton {
private:
    EagerSingleton() {};
    static EagerSingleton * instance;
public:
    static EagerSingleton* GetInstance() {
        return instance;
    }
};

EagerSingleton* EagerSingleton::instance = new EagerSingleton();

线程安全单例 && 每线程单例

在 c++ 早期标准中,为了实现线程安全的懒汉式单例需要使用双校验锁机制,如下例:

class LazySingleton {
private:
    static LazySingleton *instance;
  	static std::mutex latch_;
  	LazySingleton() {};

public:
    static auto GetInstanceWithLock() -> LazySingleton *;
};

auto LazySingleton::GetInstanceWithLock() -> LazySingleton * {
    if (LazySingleton::instance == nullptr) {
        std::lock_guard<std::mutex> lock(latch_);
        // 锁后仍需再次判断
        if (LazySingleton::instance == nullptr)
            instance = new LazySingleton();
    }
    return instance;
}

LazySingleton* LazySingleton::instance = nullptr;
std::mutex LazySingleton::latch_;

当实例被大量访问时,频繁的条件判断和加解锁会影响效率,好在在 c++11 及以后的版本,可以通过 local static 代替 双校验锁, 在下述代码中展示了在 上的两种实现 get()getPointer(),此外还可以通过 添加 thread_local 实现线程局部单例,即每线程单例

struct Database {
protected:
    /**
     * 隐藏构造函数,防止创建新实例
     */
    Database() { /* do something */ };

public:
    /**
     *  懒汉式
     *  Meyer's Singleton 的经典模式
     *  C++11 即以后版本中,函数中的 local static 对象 只有第一次访问才会创建对象,并且线程安全
     *  在 C++ 早期版本,可能需要双重校验锁来解决线程安全问题
     */
    static Database& get() {
        static Database database;
        return database;
    }

    /**
     *  我认为下面到代码可能导致内存泄露,书中提到在 Database 不会被析构这一前提下无泄漏
     *
     *  书中描述如下:
     *  The preceding implementation relies on the assumption that Database lives until the end of the program and the
     *  use of a pointer instead of a reference ensures that a destructor, even if you make one (which, if you do,
     *  would have to be public), is never called. And no, the preceding code doesn’t cause a memory leak.
     *
     *  下面到代码实现依赖于 "Database一直存在到程序结束" 这一假设。
     *  使用指针而不是引用可以确保析构函数永远不会被调用,即使定义了析构函数。
     *  这段代码不会导致内存泄漏
     *  
     *  或许采用 share_ptr 就没必要担心这个问题了
     */
    static Database& getPointer() {
        static auto* database_p = new Database();
        return *database_p;
    }
  
   /**
     * 线程局部单例,不必担心线程安全问题,因为每个线程都有一个 thread_local 对象
     * 在 Thread Information Block 中存储 一个 thread_local 副本
     */
    static Database& getPerThread() {
        thread_local Database database;
        return database;
    }

    /**
     * delete 拷贝构造、移动构造、=操作符等, 阻止创建新实例的可能性
     * 在c++11 之前可以通过将拷贝构造、赋值运算 设为私有 也可以实现同样的目的
     */
    Database(Database const&) = delete;
    Database(Database&&) = delete;
    Database& operator=(Database const&) = delete;
    Database& operator=(Database &&) = delete;
};

此外,还需注意,上述代码中通过隐藏、禁止 Big Five 以减少创建新实例的可能性

对于 c++11 标准之前的代码,可以通过将 Big Three 设为私有函数代替之

控制反转(Inversion of Control,IoC)容器实现单例

实现单例模式具有诸多不便,例如当我们由于某种需求不在使用某个类作为单例,这会给使用者带来麻烦

好在 boost::di 依赖注入框架可以帮助我们便捷使用

auto injector = di::make_injector(
  					// 每当需要使用IFoo类型的组件时,都通过使用Foo类型的单例实例来初始化IFoo类型组件
            di::bind<IFoo>().to<Foo>().in(di::singleton)
    );
#include "boost/di.hpp"
#include "boost/lexical_cast.hpp"
#include "iostream"

namespace di = boost::di;

struct IFoo
{
    virtual std::string name() = 0;
};

struct Foo : IFoo
{
    static int id;
    Foo() { ++id; }
    std::string name() override
    {
        return "foo " + boost::lexical_cast<std::string>(id);
    }
};

int Foo::id = 0;

struct Bar
{
    std::shared_ptr<IFoo> foo;
};

int main()
{
    auto injector = di::make_injector(
            di::bind<IFoo>().to<Foo>().in(di::singleton)
    );
  	// 获取Bar实例
    auto bar1 = injector.create<std::shared_ptr<Bar>>();
    auto bar2 = injector.create<std::shared_ptr<Bar>>();

    std::cout << bar1->foo->name() << std::endl;	// foo 1
    std::cout << bar2->foo->name() << std::endl;	// foo 1

    std::cout << std::boolalpha
         << (bar1->foo.get() == bar2->foo.get())	// true
         << std::endl;
}

Design Patterns in Modern C++: According to many people, using a singleton in a DI container is the only socially acceptable use of a singleton. At least with this approach, if you need to replace a singleton object with something else, you can do it in one central place: the container configuration code. An added benefit is that you won’t have to implement any singleton logic yourself, which prevents possible errors. Oh, and did I mention that Boost.DI is thread safe?

  • 许多开发者认为 Boost::di 是唯一可以接受的实现单例方式
  • 替换便捷,只需要重写配置容器的代码即可
  • 不必实现单例逻辑,防止潜在错误
  • Boost::di 的线程安全性?

作为一个尚未接触工业开发的学习者,并不清楚工业界实现单例模式的方式,但可以对其线程安全性进行简单分析: 从复杂的模板化源码中抽丝剥茧,重点关注 create() 以及 create_impl() ,不难发现 在创建一个新的实例时,采取的方式也是 Meyer’s Singleton,因此其是线程安全的,要归功于 local static

class singleton {
  template <class T, class = decltype(aux::has_shared_ptr__(aux::declval<T>()))>
  class scope_impl {
   public:
    template <class T_, class>
    using is_referable = typename wrappers::shared<singleton, T&>::template is_referable<T_>;
    template <class, class, class TProvider>
    static decltype(wrappers::shared<singleton, T&>{aux::declval<TProvider>().get(type_traits::stack{})}) try_create(
        const TProvider&);
    template <class, class, class TProvider>
    auto create(const TProvider& provider) {
      return create_impl(provider);
    }

   private:
    // Meyer's Singleton 
    template <class TProvider>
    wrappers::shared<singleton, T&> create_impl(const TProvider& provider) {
      static auto object(provider.get(type_traits::stack{}));
      return wrappers::shared<singleton, T&>(object);
    }
  };

单态模式(Monostate)

单态模式是单例模式的一种变体,行为上类似单例模式

class Printer {
  	static int id;
 public:
 	int get_id() const { return id; }
  	void set_id(int value) { id = value; }
};

事实上这只是一个提供了 get(), set()方法的普通的类,不过他们操作的都是 静态数据

这种方式中,如果使用者可以妥善的处理不同的 Printer 对象实例,那么可以实现单例模式的功能

但当用户不断创建新实例,尝试操作 static 数据时,或许我们就会怀念 “the toilet brush”

你可能感兴趣的:(单例模式,c++,开发语言,设计模式)