对于类中的静态成员的使用一直不懂,今天看了一位清华老师的C++课程中的一节课,里面专门利用单子模式对静态成员进行阐释(我不知道老师的姓名,讲的非常好,谢谢!)。下面的记录和理解都是根据我个人的理解和语言总结的,方便以后看时想起,会有不规范之处。
单子模式:在整个程序生命周期内,一个类只有一个对象,且类中的所有操作都是针对这一个对象的。
单子模式要求使用人员无法刻意构造新对象,最好也无法析构对象。因此将构造和析构函数都放在private中。下面给出几种单子模式的例子,为方便,类的声明和定义均放在头文件中。
单子模式1:
/*Singleto1.h*/
class Singleton1
{
public:
static Singleton1* GetSingle() //静态成员函数,用于产生一个非空的对象指针
{
if(!single)
{
single = new Singleton1;
}
return single;
}
int GetA() //操作验证数据,每次调用后a都像静态变量一样增加
{
return ++a;
}
private:
Singleton1(){a = 0;}
Singleton1(const Singleton1 &that);
Singleton1 & operator=(const Singleton1 &that);
~Singleton1(){;}
private:
static Singleton1* single; //定义指向该类的静态指针成员
int a; //验证数据
};
/*static*/ Singleton1* Singleton1::single = nullptr; //静态成员的赋值在类定义外面
这个类定义中,构造函数、复制构造函数、赋值操作重载函数、析构函数都放在了private中,避免了用户对单子单独行的破坏。检验运行程序如下:
#include<iostream>
#include"Singleton1.h"
using namespace std;
int main()
{
cout << "First Singleton's address is " << Singleton1::GetSingle() << endl;
cout << "First call for a is " << Singleton1::GetSingle()->GetA() << endl;
cout << "Second Singleton's address is " << Singleton1::GetSingle() << endl;
cout << "Second call for a is " << Singleton1::GetSingle()->GetA() << endl;
}
运行结果:
First Singleton's address is 0x6e0c20
First call for a is 1
Second Singleton's address is 0x6e0c20
Second call for a is 2
两次运行,单子的地址都没变,且检验数据a的结果也证明两次调用的a是在同一对象内。
这个类存在一定缺点,函数中new了指针,后面没有delete掉。如果在析构函数中delete掉,如下程序:
/*
错误的析构
*/
~Singleton1()
{
if (Singleton1::single)
{
delete Singleton1::single;
Singleton1::single = nullptr;
}
}
该析构函数无论放在private还是public下都不行,因为delete操作符执行时,本身需要调用Singleton1::single指向的类的析构函数,引起无限套用。且非静态函数不能释放静态指针成员,否则可能导致系统崩溃。
知识点1:静态成员在类的定义中只能声明,其定义在类定义的外面。
知识点2:定义static成员函数,不能用const修饰。
知识点3:静态成员的操作通过静态成员函数进行。
知识点4:通过静态成员函数访问非静态成员,必须指定对象或者使用对象的指针。因为静态成员没有默认的this指针,无法知道具体要访问的是哪个对象的非静态成员。(例子并无体现此知识点)
知识点5:非静态函数不能释放静态指针成员,否则可能会引起系统崩溃。
单子模式2:
/*Singleto2.h*/
class Singleton2
{
public:
static Singleton2* GetSingle()
{
if(!single)
{
single = new Singleton2;
}
return single;
}
int GetA()
{
return ++a;
}
private:
Singleton2(){a = 0;}
Singleton2(const Singleton2 &that);
Singleton2 & operator=(const Singleton2 &that);
~Singleton2(){;}
private:
static Singleton2* single;
int a;
//定义了一个专门析构单子的类
class Destroyer
{
public:
~Destroyer()
{
if (Singleton2::single)
{
delete Singleton2::single;
Singleton2::single = nullptr;
}
}
};
static Destroyer destroyer;
};
/*static*/ Singleton2* Singleton2::single = nullptr;
在单子类内部嵌套定义了一个Destroyer类,和Destroyer类的静态对象(对于Singleton2类,该静态对象是静态成员)。Destoryer类只用于析构单子。当程序结束时,系统自动调用Destroyer类静态成员的析构函数,同时析构了单子new出来的single指针。
知识点6:程序结束时,系统自动调用静态成员的析构函数。
该销毁只能在程序结束时进行,销毁时机不能自己掌握。且在程序结束时,系统将自动回收资源,所以销毁的动作并无意义。有些系统也可能执行不到销毁的步骤,直接释放资源。
单子模式3:
/*Singleto3.h*/
#include<cstdlib>
class Singleton3
{
public:
static Singleton3* GetSingle()
{
if(!single)
{
single = new Singleton1;
}
return single;
}
int GetA()
{
return ++a;
}
//不调用析构函数,定义Release函数释放new出来的指针,时机自行掌握
static void Release()
{
if (single)
{
free(single);
single = nullptr;
}
}
private:
Singleton1(){a = 0;}
Singleton1(const Singleton1 &that);
Singleton1 & operator=(const Singleton1 &that);
~Singleton1(){;}
private:
static Singleton1* single;
int a;
};
/*static*/ Singleton1* Singleton1::single = nullptr;
这里有一个奇怪的地方,即new出来的指针用free()释放内存,有悖于new/delete,malloc()/free()的配对使用原则。不同在于,使用delete操作符,需要调用Singleton3的析构函数;使用free()只是简单的返还内存给操作系统,跳过了析构函数。实测有效。但还是建议将
single = new Singleton1;
改为:
single = (Singleton3*)malloc(sizeof(Singleton3));
测试程序:
#include<iostream>
#include"Singleton3.h"
using namespace std;
int main()
{
cout << "First Singleton's address is " << Singleton3::GetSingle() << endl;
cout << "First call for a is " << Singleton3::GetSingle()->GetA() << endl;
cout << "Second Singleton's address is " << Singleton3::GetSingle() << endl;
cout << "Second call for a is " << Singleton3::GetSingle()->GetA() << endl;
//调用Release()函数释放内存
Singleton3::GetSingle()->Release();
cout << "Release() called." << endl;
//重新给出对象指针
cout << "Singleton's address is " << Singleton3::GetSingle() << endl;
cout << "Restart Call for a is " << Singleton3::GetSingle()->GetA() << endl;
}
测试结果:
First Singleton's address is 0x1453c20
First call for a is 1
Second Singleton's address is 0x1453c20
Second call for a is 2
Release() called.
Singleton's address is 0x1453c20
Restart Call for a is 1
可以看出,两次调用释放内存前后,单子的地址并没有变化,但是a的直在释放内存后重新开始计数。地址无变化本人理解为程序在堆区并没有利用其他内存,所以第一次释放内存后,第二次还是在原来的内存处重新建立新对象。虽然地址相同,但是两个不同的单子。
单子模式4:
/*Singleton4.h*/
class Singleton4
{
public:
static Singleton4 &GetSingle()
{
static Singleton4 single;
return single;
}
int GetA()
{
return ++a;
}
private:
Singleton4(){a = 0;}
Singleton4(const Singleton4 &that);
Singleton4 & operator=(const Singleton4 & that);
~Singleton4(){;}
private:
int a;
};
这里把在static成员函数GetSingle()内部定义单子的static对象,每次调用GetSingle()得到的都是static局部量single。因为single是一个引用,所以使用的时候可以定义另一个引用,并用GetSingle()对其赋值。虽然实际上是单子的引用,但是看起来与单子的意义有悖。
Singleton4 &single = Singleton4::GetSingle();
int n = single.GetA();
单子模式4是不能在任意位置析构的(single是static类,在静态区)。但好处在于并未在堆区new出指针,避免了归还内存的麻烦。所有静态变量在程序结束后由系统自动调用析构函数进行销毁。