5.2.5 C++11与最小垃圾回收支持
C++11新标准为了做到最小的垃圾回收支持,首先对“安全”的指针进行了定义,或者使用C++11中的术语说,安全派生(safely derived)的指针。安全派生的指针是指向由new分配的对象或其子对象的指针。安全派生指针的操作包括:
❑在解引用基础上的引用,比如:&*p。
❑定义明确的指针操作,比如:p+1。
❑定义明确的指针转换,比如:static_cast<void*>(p)。
❑指针和整型之间的reinterpret_case,比如:reinterpret_cast<intptr_t>(p)。
注意 intptr_t是C++11中一个可选择实现的类型,其长度等于平台上指针的长度(通过decltype声明)。
我们可以回头看看代码清单5-11。reinterpret_cast<long long>(p)是合法的安全派生操作,而转换后的指针再进行异或操作:reinterpret_cast<long long>(p)^2012之后,指针就不再是安全派生的了,这是因为异或操作(^)不是一个安全派生操作。同理,reinterpret_cast<long long>(q)^2012也不是安全派生指针。因此,根据定义,在使用内存回收器的情况下,*q=10的行为是不确定的,如果程序在此处发生错误也是合理的。
在C++11的规则中,最小垃圾回收支持是基于安全派生指针这个概念的。程序员可以通过get_pointer_safety函数查询来确认编译器是否支持这个特性。get_pointer_safety的原型如下:
pointer_safety get_pointer_safety()noexcept
其返回一个pointer_safety类型的值。如果该值为pointer_safety::strict,则表明编译器支持最小垃圾回收及安全派生指针等相关概念,如果该值为pointer_safety::relax或是pointer_safety::preferred,则表明编译器并不支持,基本上跟没有垃圾回收的C和C++98一样。不过按照一些解释,pointer_safety::preferred和pointer_safety::relax也略有不同,前者垃圾回收器可能被用作一些辅助功能,如内存泄露检测或检测对象是否被一个错误的指针解引用(事实上,在本书编写时,几乎没有编译器实现了最小垃圾回收支持,甚至连get_pointer_safety这个函数接口都还没实现)。
此外,如果程序员代码中出现了指针不安全使用的状况,C++11允许程序员通过一些API来通知垃圾回收器不得回收该内存。C++11的最小垃圾回收支持使用了垃圾回收的术语,即需声明该内存为“可到达”的。
void declare_reachable(void*p);
template<class T>Tundeclare_reachable(Tp)noexcept;
declare_reachable()显式地通知垃圾回收器某一个对象应被认为可达的,即使它的所有指针都对回收器不可见。undeclare_reachable()则可以取消这种可达声明。针对代码清单5-11,我们对隐藏的指针做一些声明,如代码清单5-12所示。
代码清单5-12
include <memory>
using namespace std;
int main(){
int*p=new int;
declare_reachable(p);//在p被隐藏之前声明为可达的
intq=(int)((long long)p^2012);
//解除可达声明
q=undeclare_reachable<int>((int*)((long long)q^2012));
*q=10;
}
代码清单5-12可能是一个能够运行的例子。这里,我们在p指针被不安全派生(隐藏)之前使用declare_reachable声明其是可达的。这样一来,它会被垃圾回收器忽略而不会被回收。而在我们通过可逆的异或运算使得q指针指向p所指对象时,我们则使用了undeclare_reachable来取消可达声明。注意undeclare_reachable不是通知垃圾回收器p所指对象已经可以回收。实际上,declare_reachable和undeclare_reachable只是确立了一个代码范围,即在两者之间的代码运行中,p所指对象不会被垃圾回收器所回收。
这里可能有的读者会注意到一个细节,declare_reachable只需要传入一个简单的void指针,但undeclare_reachable却被设计为一个函数模板。这是一个极不对称的设计。但事实上undeclare_reachable使用模板的主要目的是为了返回合适类型以供程序使用。而垃圾回收器本来就知道指针所指向的内存的大小,因此declare_reachable传入void指针就已经足够了。
有的时候程序员会选择在一大片连续的堆内存上进行指针式操作,为了让垃圾回收器不关心该区域,也可以使用declare_no_pointers及undeclare_no_pointers函数来告诉垃圾回收器该内存区域不存在有效的指针。
void declare_no_pointers(char*p,size_t n)noexcept;
void undeclare_no_pointers(char*p,size_t n)noexcept;
其使用方式与declare_reachable及undeclare_reachable类似,不过指定的是从p开始的连续n的内存。