一、四种对象的作用域与生存期
- 栈对象:隐含调用构造函数(程序中没有显示调用)
- 堆对象:隐含调用构造函数(程序中没有显示调用)
- 全局对象、静态全局对象:全局对象的构造先于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的时候,它就会直接返回先前初始化的对象。注意:这个单例模式不是线程安全的。单例模式实现规则总结:禁止拷贝,提供一个全局的访问点。