c++ 单例的几种写法 singleton

单例的存在形式可以是,static对象,static指针,static智能指针。

static 对象相当于直接把对象生成在了栈中,不需要考虑生成时的多线程安全问题。

static指针在生成时需要注意只初始化一次,可以用call_once 或mutx实现线程安全。是用在多线程同时要访问改对象的场景。

static智能指针,智能指针只支持转移构造函数,不支持复制构造函数,所以全局改对象的指针只会存在在一个人的手里,而且自动析构。

由于单例往往是要写成模版类共其他类继承,所以需要定义单例模版类。

由于模版类无法自己编译,不能单独编译,所以只能存在头文件。

但是头文件也可以在头文件中include其他文件,从而在实现在看起来好像声明与定义分离了一样。

1 智能指针,call_once, 单文件模版类

Singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

#include <memory>
#include <mutex>

template<class T>
class Singleton
{
  public:
    static T* getInstancePtr();

  protected:
  // 这样搞就禁止了在外部创建对象,
    Singleton() {};
    ~Singleton() {};

  private:
    // c98
    // 在private中声明复制构造函数,但是不定义,这样就禁止了这个类被拷贝。
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    // c11
    // 可以用delete声明这个函数禁用
    // CopyBan(const CopyBan&) = delete;
    // CopyBan& operator=(const CopyBan&) = delete;

  private:
    // 使用智能指针自动析构
    static std::unique_ptr<T> _instance;
};

template<class T>
T* Singleton<T>::getInstancePtr()
{
  static std::once_flag flag;
  std::call_once(flag, [&]() {
    _instance.reset(new T());
  });
  return _instance.get();
}

template<typename T>
std::unique_ptr<T> Singleton<T>::_instance = NULL;

#endif // !SINGLETON_H

简单说明:

public 里给出获取单例对象的接口 static T* getInstancePtr()

protected 中定义了构造函数和析构函数,使得无法在类外部访问构造函数,也就禁止了在类外生成对象。

private 声明了复制构造函数和转移构造函数,使得无法通过这两种方式生成对象。

使用智能指针自动管理对象的析构。同时由于智能指针只允许转移构造,不允许复制构造,可以保证同一时间只会有一个进程拿着这个指针。

在类外定义声明的静态函数时,不应该再加static了,并且要用模版类T给实现类,即getInstancePtr前面那个类得加\<T>

最后使用空指针初始化了智能指针。

2 将声明与定义分离

Singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

#include <memory>
#include <mutex>

template<class T>
class Singleton
{
  public:
    static T* getInstancePtr();

  protected:
  // 这样搞就禁止了在外部创建对象,
    Singleton() {};
    ~Singleton() {};

  private:
    // c98
    // 在private中声明复制构造函数,但是不定义,这样就禁止了这个类被拷贝。
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    // c11
    // 可以用delete声明这个函数禁用
    // CopyBan(const CopyBan&) = delete;
    // CopyBan& operator=(const CopyBan&) = delete;

  private:
    // 使用智能指针自动析构
    static std::unique_ptr<T> _instance;
};

#include "Singleton.tpp"

template<typename T>
std::unique_ptr<T> Singleton<T>::_instance = NULL;

#endif // !SINGLETON_H

Singleton.tpp

template<class T>
T* Singleton<T>::getInstancePtr()
{
  static std::once_flag flag;
  std::call_once(flag, [&]() {
    _instance.reset(new T());
  });
  return _instance.get();
}

简答说明:

直接在头文件中include了tcpp文件,其实就是直接把tpp的内容放入了头文件中,但是在要定义的函数比较多的时候,看起来会清晰一点,

模版类由于不能单独编译,所以不存在cpp文件。

其实也不是这样说,你如果能够确定所有会使用这个模版类的类别,那么你可以直接在最后声明一下要实现的类,然后单独编译,只不过只能支持简单的类别,比如int,string,等等,如果你自己写的要继承这个模版类的类,那么你在这边就要引用那边类的头文件,那边又要引用这边的头文件,就形成交叉引用了,所以可以直接认为模版类不能单独编译是一点问题都没有的。

3 直接单例对象

class Singleton {
    private: Singleton() { }
    Singleton(const Singleton &) = delete;
    Singleton(const Singleton &&) = delete;
    Singleton &operator=(const Singleton &) = delete;

    public: static Singleton &getInstance() {
        static Singleton s;
        return s;
    }

    public: void test() {
        std::cout << "test" << std::endl;
    }
};
文章目录