什么是单例模式
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉模式
饿汉模式,就是比较饥饿,在创建静态对象指针时已经创建对象实例。所以该种单例模式时线程安全的,对象在程序运行时已经创建,之后其他代码获取的单例对象均为运行开始时创建好的对象。
class Singleton
{
public:
static Singleton* getInstance()
{
return instance;
}
private:
Singleton() {}
static Singleton *instance;
};
Singleton* Singleton::instance = new Singleton;
懒汉模式
懒汉模式,就是比较懒,只有在需要获取单例对象时才创建。这种创建方式只会在第一次调用时创建对象。
class Singleton
{
public:
static Singleton* getInstance()
{
if(nullptr == instance)
instance = new Singleton;
return instance;
}
private:
Singleton() {}
static Singleton *instance;
};
Singleton* Singleton::instance = nullptr;
但是懒汉模式是线程不安全的,所以需要在创建对象时加锁。
class Singleton
{
public:
static Singleton* getInstance()
{
if (nullptr == instance)
{
mutex.lock();
if (nullptr == instance)
{
instance = new Singleton;
}
mutex.unlock();
}
return instance;
}
private:
Singleton() {}
static Singleton *instance;
static std::mutex mutex;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
优化单例模式实现
由于上述的单例实现方式均使用指针形式申请对象,却没有显示调用delete去释放申请的对象内存,存在内存问题。
可能有的认为多加个析构函数,在析构函数中释放内存即可,则会出现以下情况。
以懒汉模式为例子:
class Singleton
{
public:
static Singleton* getInstance()
{
if (nullptr == instance)
{
mutex.lock();
if (nullptr == instance)
{
instance = new Singleton;
}
mutex.unlock();
}
return instance;
}
~Singleton()
{
cout << "~Singleton" << endl;
if(nullptr != instance) {
delete instance;
instance = nullptr;
}
}
private:
Singleton() {}
static Singleton *instance;
static std::mutex mutex;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
观察以上析构函数实现,在程序退出时可以得到以下输出结果:
~Singleton
~Singleton
~Singleton
~Singleton
~Singleton
……一直打印~Singleton
原因是在delete调用时,会调用析构函数,就会嵌套调用,循环不止。
解决办法可以有以下几种。
创建垃圾回收的私有类
在私有成员中加入垃圾回收的类,在程序结束后将Singleton的对象释放。
以懒汉模式为例子:
class Singleton
{
public:
static Singleton* getInstance()
{
if (nullptr == instance)
{
mutex.lock();
if (nullptr == instance)
{
instance = new Singleton;
}
mutex.unlock();
}
return instance;
}
private:
Singleton() {}
static Singleton *instance;
class Garbage
{
public:
Garbage() {}
~Garbage()
{
if (nullptr != instance)
{
delete instance;
instance = nullptr;
}
}
};
static Garbage garbage;
static std::mutex mutex;
};
Singleton* Singleton::instance = nullptr;
Singleton::Garbage Singleton::garbage;
std::mutex Singleton::mutex;
Garbage由于为静态变量,在程序结束后会进入析构函数,然后将Singleton的堆对象进行释放。
声明静态单例对象
将静态单例指针声明为静态单例对象
饿汉模式
class Singleton
{
public:
static Singleton* getInstance()
{
return &instance;
}
private:
Singleton();
static Singleton instance;
};
Singleton Singleton::instance;
懒汉模式
class Singleton
{
public:
static Singleton* getInstance()
{
mutex.lock();
static Singleton instance;
mutex.unlock();
return &instance;
}
private:
Singleton();
static std::mutex mutex;
};
std::mutex Singleton::mutex;
当使用C++11,GCC>4.3,VS2015以上环境时,使用静态局部变量可以保证线程安全,不需要进行加锁操作。
返回单例指针可能存在的问题
当单例对象返回为指针时,调用的指针是可以对指针的值进行,若使用如下:
int main()
{
Singleton *ptr = Singleton::getInstance();
delete ptr;
}
当获取指针后,在调用某处使用了delete或者改变ptr指向的值时,单例对象就会出现异常无法使用。所以单例模式。
最推荐的单例写法
饿汉模式
class Singleton
{
public:
static Singleton& getInstance()
{
return instance;
}
private:
Singleton() {}
static Singleton instance;
};
Singleton Singleton::instance;
懒汉模式
class Singleton
{
public:
static Singleton& getInstance()
{
mutex.lock();
static Singleton instance;
mutex.unlock();
return instance;
}
private:
Singleton() {}
static std::mutex mutex;
};
std::mutex Singleton::mutex;
返回引用的单例模式实现,可以保证单例对象在程序运行过程中都可正常使用,不被破坏。
该实现的缺点就是返回过程中会调用一次拷贝构造函数进行对象的返回。
以上均是个人愚见,如果有哪里说得不对的地方请大家指出。