目录
第12章 动态内存
12.1 动态内存与智能指针
12.1.6 weak_ptr
12.2 动态数组
12.2.1 new和数组
12.2.2 allocator类
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。
当我们创建一个weak ptr时,要用一个shared ptr来初始化它:
auto p = make_shared(42);
weak_ptr wp(p);
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared ptr。
if (shared_ptr np = wp.lock()) {
// 只有当1ock调用返回true时我们才会进入if语句体
}
为了让new分配一个对象数组,我们要在类型名之后跟一对方括号,在其中指明要分配的对象的数目。方括号中的大小必须是整型,但不必是常量。
//调用get size确定分配多少个int
int *pia = new int[get size()]; //pia指向第一个int
也可以用一个表示数组类型的类型别名来分配一个数组,这样,new表达式中就不需要方括号了:
typedef int arrT[42]; //arrT表示42个int的数组类型
int *p = new arrT; //分配一个42个int的数组;p指向第一个int
int *p = new int[42]; //编译器执行这个表达式时还是会用new[]
typedef用法:【C/C++】中【typedef】用法大全_c++中typedef的用法
当用new分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。即使我们使用类型别名定义了一个数组类型,new也不会分配一个数组类型的对象。在上例中,new返回的是一个元素类型的指针。
由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin或end。这些函数使用数组维度来返回指向首元素和尾后元素的指针。出于相同的原因,也不能用范围for语句来处理动态数组中的元素。
初始化动态分配对象的数组:默认情况下,new分配的对象,不管是单个分配的还是数组中的,都是默认初始化的。可以对数组中的元素进行值初始化方法是在大小之后跟一对空括号。在新标准中,我们还可以提供一个元素初始化器的花括号列表。
int* pia = new int[10]; //10个未初始化的int
int* pia2 = new int[10](); //10个值初始化为0的int
int* pia3 = new int[10]{ 0,1,2,3,4,5,6,7,8,9 }; //10个1nt分别用列表中对应的初始化器初始化
string* pia4 = new string[10]{ "a","an","the",string(3,'x') }; //10个string,前4个用给定的初始化器初始化,剩余的进行值初始化
动态分配一个空数组是合法的:当我们用new分配一个大小为0的数组时,new返回一个合法的非空指针,但此指针不能解引用一毕竟它不指向任何元素。
char arr[0]; // 错误:不能定义长度为0的数组
char* cp = new char[0]; //正确:但cp不能解引用
释放动态数组:为了释放动态数组,我们使用一种特殊形式的delete一在指针前加上一个空方括号对。当我们释放一个指向数组的指针时,空方括号对是必需的:它指示编译器此指针指向一个对象数组的第一个元素。如果我们在delete一个指向数组的指针时忽略了方括号其行为是未定义的。
delete cp; // p必须指向一个动态分配的对象或为空
delete [] cp; //pa必须指向一个动态分配的数组或为空
智能指针和动态数组:标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个
unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号。
当一个unique ptr指向一个数组时,我们不能使用点和箭头成员运算符。毕竟unique ptr指向的是一个数组而不是单个对象,因此这些运算符是无意义的。当一个unique ptr指向一个数组时,我们可以使用下标运算符来访问数组中的元素。
// up指向一个包含10个未初始化int的数组
unique_ptr up(new int[10]);
for (size_t i = 0; i != 10; ++i) {
up[i] = i;
cout << "up" << "[" << i << "]:" << up[i] << endl;
}
//自动用delete[]销毁其指针
up.release();
与unique_ptr不同,shared_ptr不直接支持管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器。
shared_ptr未定义下标运算符,而且智能指针类型不支持指针算术运算。因此,为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素。
allocator类:标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。
为了定义一个allocator对象,我们必须指明这个allocator可以分配的对象类型。当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置。
allocator alloc; //可以分配string的allocator对象
auto const p = alloc.allocate(8); //分配n个未初始化的string
allocator分配未构造的内存:allocator分配的内存是未构造的(unconstructed)。我们按需要在此内存中构造对象。在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定
位置构造一个元素。
auto q = p; // q指向最后构造的元素之后的位置
alloc.construct(q++); //*q为空字符串
alloc.construct(q++, 10, 'c'); //*q为cccccccccc
alloc.construct(q++, "hi"); //*g为hi!
为了使用allocate返回的内存,我们必须用construct构造对象。使用未构造的内存,其行为是未定义的。
当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。函数destroy接受一个指针,对指向的对象执行析构函数:
while(q!=p)
alloc.destroy(--q); //释放我们真正构造的string
在循环开始处,q指向最后构造的元素之后的位置。我们在调用destroy之前对q进行了递减操作。因此,第一次调用destroy时,q指向最后一个构造的元素。最后一步循环中我们destroy了第一个构造的元素,随后g将与p相等,循环结束。
释放内存通过调用deallocate来完成
alloc.deallocate(p, n);
拷贝和填充未初始化内存的算法 :标准库还为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象,它们都定义在头文件memory中。
uninitialized_copy(b,e,b2) | 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中元素的拷贝 |
uninitialized_copy_n(b,n,b2) | 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中 |
uninitialized_fill(b,e,t) | 在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝 |
uninitialized_fill_n(b,n,t) | 从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象 |