延迟构造实例,简单来说就是一个类在没有用到的时候先占着一个坑,只有当真正用到的时候才开始倒入资源开始构造。最常见的就是我们平时用到的单例模式,实现的代码如下:
这个类的只有在调用GetInstance才对 m_pInstance进行初始化,就体现了用到才实例化的思想。这样做的好处是加速程序的启动过程,比如一个界面的菜单对象,当你还没有点击菜单之前就创造了,那他一定会影响软件的启动性能。在chromium中,实现延迟构造的是一个LazyInstance类,这个类比起上边这个类复杂很多,我起初也觉得上边的单例也很足够了,在深入了解LazyInstance类后,我却感觉我的见识还是太少了。
lazy_instance.h
lazy_instance.cc
1~12 定义DefaultLazyInstanceTraits,本身并不复杂,就是实现Type类型的构造和析构。与通常new Type()不同,它需要参数传入参数instance,这是一块可用的内存空间,7行就是在这块可用的内存空间中构造Type实例。比较难懂的只有第5行,reinterpret_cast(instance)把instance指针地址转成一个整数,ALIGNOF(Type)计算Type的对齐要求,reinterpret_cast(instance) & (ALIGNOF(Type) - 1)实际上相当于计算instance物理地址位置能不能被ALIGNOF(Type)整除,也就是满足对齐要求。举例来说就是,如果Type要求8字节对齐(如double)而给出的地址不能被8整除,就会报错(程序中的报错被我简化的)。
14~22定义DefaultLazyInstanceTraits,可以注意到它与DefaultLazyInstanceTraits的区别只是在于它Delete是空的。默认情况下,LazyInstance使用的是DefaultLazyInstanceTraits,它把Delete注册到AtExitManager,在程序退出时执行Delete析构。
31~74是最晦涩的一段定义,Pointer是从LazyInstance中取出实例的指针,这也是LazyInstance的核心实现
42行value中的值就是private_instance的值,在private_instance被附值之前,它保存的是LazyInstance的状态,private_instance_的初始化为0,也就是说private_instance_没有被初始化。
43~44 !(value & kLazyInstanceCreatedMask)这个分两种情况,
a、某个线程当第一次执行该语句,value的值为0,判断结果为真,进入到NeedsLazyInstance,查看lazy_instance.cc第2行,当value等于0时,把kLazyInstanceStateCreating附值给value,说明LazyInstance在实例化过程中,返回true。if语句通过,进入实例化private_instance_的阶段45~48,在实例化完成后,在CompleteLazyInstance中把实例化后的指针附值给private_instance_,表示实例化已经完成,整个过程private_instance_的值的变化是0->kLazyInstanceStateCreating->private_buf_.void_data。
b、当某一个线程在其它线程实例化过程中调用了Pointer函数,这时value的值已经被附值成kLazyInstanceStateCreating,这时候依然进入NeedsLazyInstance,但是这时value的值已经不是0,进入lazy_instance.cc的5~7行,等待实例初始化完成,返回false。该线程不会进入实例化的代码,直接返回instance()。
到此为止,代码的讲解已经完成,我觉得我的单例也蛮好的,为什么实现这么一个LazyInstance?
(1)无锁但是线程安全,在LazyInstance涉及数据竞争的操作只有NoBarrier_CompareAndSwap,但是InterlockedCompareExchange使用的是原子操作,原子操作在指令运行过程中阻止了其他线程对内存进行操作,这也可以理解成一种锁,但是速度会比正常的锁快。这种无锁的实现方式其实加大了代码的理解难度,这里主要应该是为了效率考虑的。从代码中可以看出,在初始化完成后,所有的代码都没有锁操作,相比通常加互斥锁或者读写锁,效率应该是更快的。
(2)全局变量且满足POD特性,在静态区保存,避免产生堆区的内存碎片。在LazyInstance的实现中,它是满足POD特性,POD特性简单来说就是组成对象的内存是连续的,可以直接使用memcopy进行拷贝,对象的大小是固定的。POD更详细的说明可以参考http://zh.wikipedia.org/wiki/POD_(%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1)。LazyInstance由于满足POD特性,在定义为全局变量时,它的数据都保存在静态区,并且已经为它的类型保留有空间private_buf_,从而避免使用对象时,使用new操作在堆空间中申请内存,产生碎片,而且达到了延迟构造实例的目的。
转注:有关POD特性 再看下面的转载http://blog.chinaunix.net/uid-20726927-id-2455477.html
(C++-98:1.8;5)给出的定义:
将对象的各字节拷贝到一个字节数组中,然后再将它重新拷贝到原先的对象所占的存储区中,此时该对象应该具有它原来的值。
《imperfect c++》一书中给出的定义和一些特性利用:
1、所有标量类型、POD结构类型、POD联合类型、以及这几种类型的数组、const/volatile修饰的版
本都是POD类型。
2、POD结构/联合类型:一个聚合体(包括class),它的非static成员都不是pointer to member、
pointer to member function、非POD结构、非POD联合,以及这些类型的数组、引用、const/
volatile修饰的版本;并且,此聚合体不能有用户自定义constructor、destructor、assign
operator。
3、POD类型可以具有static成员、成员typedef、嵌套struct/class定义和成员函数/方法。
POD类型特性:
所有POD类型都可以作为union的成员,反之,所有非POD类型都不能作为union的成员。
POD特性利用:
我们可以利用POD类型特性来判断一个类型是否为POD类型:
template <typename Type>
struct must_be_pod
{
union
{
Type noname;
};
};
这个模板的意思是,只要类型Type是非POD类型,那么编译器将报错,因为Type被作为了union的一个成员。
使用示例:
class MyClass
{
public:
MyClass() {}
~MyClass() {}
private:
virtual void fun() {}
};
template <typename Type>
struct must_be_pod
{
union
{
Type noname;
};
};
int main()
{
must_be_pod<double> must_be_pod_; // 正确
// 编译报错
// warning C4624: “must_be_pod<MyClass>”: 已将析构函数隐式定义为“已删除”
// error C2280: “must_be_pod<MyClass>::must_be_pod(void)”: 尝试引用已删除的函数
// 把 MyClass 的构造函数 析构函数 虚函数注释掉才可以编译通过
must_be_pod<MyClass> must_be_pod_myclass;
return 0;
}
你可以将 POD 类型看作是一种来自外太空的用绿色保护层包装的数据类型,POD 意为“Plain Old Data”(译者:如果一定要译成中文,那就叫“彻头彻尾的老数据”怎么样!)这就是 POD 类型的含义。其确切定义相当粗糙(参见 C++ ISO 标准),其基本意思是 POD 类型包含与 C 兼容的原始数据。例如,结构和整型是 POD 类型,但带有用户定义的构造函数或虚拟函数的类则不是。 POD 类型没有虚拟函数,基类,用户定义的构造函数,拷贝构造,赋值操作符或析构函数。
为了将 POD 类型概念化,你可以通过拷贝其比特来拷贝它们。此外, POD 类型可以是非初始化的。例如:
struct RECT r; // value undefined非 POD 类型通常需要初始化,不论是调用缺省的构造函数(编译器提供的)还是自己写的构造函数。
POINT *ppoints = new POINT[100]; // ditto
CString s; // calls ctor ==> not POD
过去, POD 对于编写编译器或与C 兼容的 C++ 程序的人来说很重要。现在,POD 来到 .NET 的环境中。在托管 C++ 中,托管类型(包括 __value 和 __gc 两者)能包含嵌入的原生 POD 类型。 Figure 3 展示了例举说明代码。托管的 Circle 类能包含 POINT,但无法包含 CPoint 类。如果你尝试编译 pod.cpp 会报一个 C3633 错误:“Cannot define ''m_center'' as a member of managed ''Circle'' because of the presence of default constructor ''CPoint::CPoint'' on class ''CPoint''.”(译者:意思是由于类 CPoint 有缺省的构造函数‘CPoint::CPoint’,所以不能将‘m_center’定义为托管类‘Circle’的一个成员)
.NET 限定嵌入的本地对象只能为 POD 类型的理由是这样做能安全地拷贝它们,不用担心调用构造函数,初始化虚表,或任何非 POD 类型需要的其它机制。