知识准备:
类的静态成员变量必须得在类外初始化
由于静态变量在编译期间必须定义(分配空间),静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。类内的静态成员变量若没有类的外面定义,是没有分配内存的。而类内的静态函数编译完就分配内存,就可以直接调用;类内的静态函数中的静态局部变量,是在第一次调用函数的时候初始化。
但在C和C++中静态局部变量的初始化节点又有点不太一样。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。而在C++中,初始化时在执行相关代码时才会进行初始化,主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的。
能在类中初始化的成员只有一种,那就是静态整型常量成员。
下面代码实现了三种常见的单例模式
#include <iostream>
#include "pthread.h"
using namespace std;
pthread_mutex_t g_single_lock;
class Single
{
public:
//函数内的局部静态变量在编译时已经分配空间,在第一次函数跑到这里时初始化,之后不再初始化
//多线程访问时,若不加锁,可能会出现多次初始化的问题,但是地址是不会变的
static Single *get_instance1()
{
static Single instance;
return &instance;
}
static Single *get_instance2()
{
//double check 防止多线程每次都要加锁,影响性能
if(NULL == m_instance)
{
pthread_mutex_lock(&g_single_lock);
if(NULL == m_instance)
{
m_instance = new Single();
}
pthread_mutex_unlock(&g_single_lock);
return m_instance;
}
}
static Single &get_instance3()
{
static Single instance;
return instance;
}
void Print()
{
cout<<"Print"<<endl;
}
private:
Single()
{
cout<<"Single construct"<<endl;
};
Single(Single&)=delete;
static Single *m_instance;
Single & operator = (const Single&)=delete;
};
//类的静态成员变量必须得在类外初始化
Single* Single::m_instance = NULL;
int main()
{
pthread_mutex_init(&g_single_lock, NULL);
Single *instance1 = Single::get_instance1();
Single *instance2 = Single::get_instance2();
Single &instance3 = Single::get_instance3();
instance1->Print();
instance2->Print();
instance3.Print();
return 0;
}
//单线程
class singleTon{
public:
static singleTon* getInstance(){
if (nullptr ==m_instance){
m_instance = new singleTon();
}
return m_instance;
}
~singleTon(){
}
private:
singleTon(){}
singleTon(const singleTon &other){}
static singleTon * m_instance;//这里仅仅是声明
};
//静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存
//类的静态成员变量需要在类外分配内存空间
singleTon *singleTon::m_instance = nullptr;
//多线程上述线程不安全,可以的话,在系统初始化使get一次
//单线程
class singleTon{
public:
//函数内的局部静态变量在编译时已经分配空间,在第一次函数跑到这里时初始化,之后不再初始化
//多线程访问时,若不加锁,可能会出现多次初始化的问题(部分编译器会有优化),但是地址是不会变的
static singleTon* getInstance(){
static singleTon singleTonInstncae;
return &singleTonInstncae;
}
~singleTon(){
}
private:
singleTon(){}
singleTon(const singleTon &other){}
};
class Lock{
public:
Lock(){
//初始化锁
}
~Lock(){
//锁的释放
}
};
//多线程 性能不佳
class singleTon{
public:
static singleTon* getInstance(){
Lock lock;
if (nullptr ==m_instance){
m_instance = new singleTon();
}
return m_instance;
}
private:
singleTon(){}
singleTon(const singleTon &other){}
static singleTon * m_instance;
};
singleTon *singleTon::m_instance = nullptr;
/*多线程 double check lock 有时候编译器优化:内存读写reorder导致双检查锁失效
//在指令层,new singleTon()操作假设有如下步骤:
//分配内存 初始化(构造函数)m_instance赋值
编译器可能优化为 分配内存 m_instance赋值 初始化(构造函数)
若A线程执行到m_instance赋值,还未初始化,B线程拿到m_instance使用,此时会有问题
*/
class singleTon{
public:
static singleTon* getInstance(){
if(nullptr == m_instance){
Lock lock;
if (nullptr ==m_instance){
m_instance = new singleTon();
}
}
return m_instance;
}
private:
singleTon(){}
singleTon(const singleTon &other){}
static singleTon * m_instance;
};
singleTon *singleTon::m_instance = nullptr;