十三、对象的使用(二)四种对象的作用域与生存期、static用法总结、static与单例模式

一、四种对象的作用域与生存期

  • 栈对象:隐含调用构造函数(程序中没有显示调用)
  • 堆对象:隐含调用构造函数(程序中没有显示调用)
  • 全局对象、静态全局对象:全局对象的构造先于main函数

已初始化的全局变量或静态全局对象存储于.data段中

未初始化的全局变量或静态全局对象存储于.bss段中
  • 静态局部对象

已初始化的静态局部变量存储于.data段中

未初始化的静态局部变量存储于.bss段中

内存四区模型:


下面的例子说明了对象的声明周期和作用域:

#include <iostream>
using namespace std;class Test
{
public:Test(int n) : n_(n){cout<<"Test "<<n_<<" ..."<<endl;}~Test(){cout<<"~Test "<<n_<<" ..."<<endl;}
private:int n_;
};int n;			// 未初始化的全局变量,初始值为0。n存储于.bss段中。(block started by symbol)
int n2 = 100;	// 已初始化的全局变量,初始值为100。n2存储于.data段中。
Test g(100);		// 全局对象的构造先于main函数
static Test g2(200);int main(void)
{cout<<"Entering main ..."<<endl;Test t(10);		// 栈上创建的对象,在生存期结束的时候自动释放{Test t(20);}{Test* t3 = new Test(30);	// 堆上创建的对象,要显示释放delete t3;}{static int n3;		// n3存储于.bss段中		(编译期初始化)static int n4 = 100;	// n4存储于.data段中	(编译期初始化)static Test t4(333);	// t4对象运行期初始化	.data段}cout<<"Exiting main ..."<<endl;
}

注意:作用域并不等同于声明周期。如static int n3具有代码块作用域,但是存在于程序的整个生命周期。

二、static用法总结

上一篇文章已经讲了static在C和C++的用法,这里在总结一下。

C语言static用法:

1. 用于函数内部修饰变量,即函数内的静态变量。这种变量的生存期长于该函数,使得函数具有一定的“状态”。使用静态变量的函数一般是不可重入的,也不是线程安全的,比如strtok(3)。

2. 用在文件级别(函数体之外),修饰变量或函数,表示该变量或函数只在本文件可见,其他文件看不到也访问不到该变量或函数。专业的说法叫“具有internal linkage”(简言之:不暴露给别的translation unit)。

由于C++引入了类,在保持与C语言兼容的同时,static关键字又有了两种新用法,C++语言static用法:

3.用于修饰类的数据成员,即所谓“静态成员”。这种数据成员的生存期大于class的对象(实例/instance)。静态数据成员是每个class有一份,普通数据成员是每个instance 有一份。
4. 用于修饰class的成员函数,即所谓“静态成员函数”。这种成员函数只能访问静态成员和其他静态程员函数,不能访问非静态成员和非静态成员函数

三、static与单例模式

          有些对象是独一无二的,我们不希望它能被复制,即我们希望在程序的整个生命周期只存在一个这样的对象,这时候我们可以使用单例模式。

#include <iostream>
using namespace std;class Singleton
{
public:static Singleton* GetInstance(){if (instacne_ == NULL){instacne_ = new Singleton;}return instacne_;}~Singleton(){cout<<"~Singleton ..."<<endl;}
private:Singleton(const Singleton& other);Singleton& operator=(const Singleton& other);Singleton(){cout<<"Singleton ..."<<endl;}static Singleton* instacne_;
};Singleton* Singleton::instacne_;int main(void)
{//Singleton s1;//Singleton s2;Singleton* s1 = Singleton::GetInstance();Singleton* s2 = Singleton::GetInstance();//Singleton s3(*s1);		// 调用拷贝构造函数return 0;
}

如果此对象不存在,就创建一个对象并返回,如果已经存在,直接返回该对象。但是这个版本有些bug,就是new的对象并没有释放。当然我们可以再做一个静态释放对象的函数,但是让对象自己释放空间更好一些。

可以利用对象的确定性析构来释放new出来的对象。

#include <iostream>
using namespace std;class Singleton
{
public:static Singleton* GetInstance(){if (instacne_ == NULL){instacne_ = new Singleton;}return instacne_;}~Singleton(){cout<<"~Singleton ..."<<endl;}//static void Free()//{//	if (instacne_ != NULL)//	{//		delete instacne_;//	}//}class Garbo{public:~Garbo(){if (Singleton::instacne_ != NULL){delete instacne_;}}};
private:Singleton(const Singleton& other);Singleton& operator=(const Singleton& other);Singleton(){cout<<"Singleton ..."<<endl;}static Singleton* instacne_;static Garbo garbo_;	// 利用对象的确定性析构
};Singleton::Garbo Singleton::garbo_;
Singleton* Singleton::instacne_;int main(void)
{Singleton* s1 = Singleton::GetInstance();Singleton* s2 = Singleton::GetInstance();return 0;
}

在上面的例子中为了实现单例模式引入了很多外部代码,看着不够简洁,我们可以使用静态局部对象的特点实现更简单的写法:

#include <iostream>
using namespace std;class Singleton
{
public:static Singleton& GetInstance(){static Singleton instance;		// 局部静态对象return instance;}~Singleton(){cout<<"~Singleton ..."<<endl;}
private:Singleton(const Singleton& other);Singleton& operator=(const Singleton& other);Singleton(){cout<<"Singleton ..."<<endl;}
};
int main(void)
{Singleton& s1 = Singleton::GetInstance();Singleton& s2 = Singleton::GetInstance();return 0;
}

局部静态变量在运行期初始化,而且它是有状态的变量,只能被初始化一次。所以当我们第二次调用getInstance的时候,它就会直接返回先前初始化的对象。注意:这个单例模式不是线程安全的。
单例模式实现规则总结:禁止拷贝,提供一个全局的访问点。