12.1动态内存与智能指针
智能指针类型:
shared_ptr
:允许多个指针指向同一对象(指向同一对象的多个指针管理不善会出问题)unique_ptr
:独占指向的对象weak_ptr
:指向shared_ptr
管理的对象
12.1.1 shared_ptr类
与vector
一样,智能指针也是模板,在尖括号中给出类型,如:
shared_ptr<string> p1;
默认初始化的智能指针为空指针。智能指针与普通指针使用方式一致。解引用可以返回其指向的对象。
// 如果p1不为空, 检查它指向的字符串是否为空
if(p1 && p1->empty()){
*p1 = "hi";
}
shared_ptr与unique_ptr支持的操作
make_shared函数
最安全的分配和使用动态内存的方法是调用一个名为make_shared
的标准库函数,函数在memory
头文件中。
// p4指向一个"9999999999"的string
shared_ptr<string> p4 = make_shared<string>(10, '9');
通常使用auto
定义一个对象保存make_shared
的结果:
auto p6 = make_shared<vector<string>>();//空的vector<string>
shared_ptr的拷贝与赋值
每个shared_ptr
都有一个相关的引用计数,到底是用计数器还是其他数据结构来记录有多少个指针共享对象完全由标准库决定。
引用计数递增情况:
- 用一个
shared_ptr
初始化另一个shared_ptr
- 将
shared_ptr
作为参数传递给一个函数 - 作为函数的返回值
引用计数递减情况:
shared_ptr
赋一个新值- 局部
shared_ptr
离开作用域
如果一个对象的引用计数变为0,则shared_ptr
通过对象的析构函数销毁对象。
12.1.2 直接管理内存(new delete)
使用new动态分配和初始化对象
默认情况下,动态分配的对象是默认初始化的。这意味着:
- 内置类型与组合类型的对象值是未定义的,就是不知道是啥玩意儿。如
int* p = new int;
,p所指想的对象值不知道是什么。 - 类类型的对象调用默认的构造函数进行初始化。
如果要对动态分配的对象进行初始化,可以使用括号初始化或者列表初始化或者调用类的构造函数进行初始化。
如:string* s1 = new string();
使用string
的默认构造函数初始化一个空串。
一个动态分配的const
对象必须被初始化。
内存耗尽
如果new
不能分配所需要的空间,则直接抛出一个bad_alloc
异常,该异常在头文件new
中。
当然也可以阻止其抛出异常,如:
int* p2 = new (nothrow) int;//如果分配失败,返回一个空指针,不抛出异常
这种形式成为定位new(placement new)。
指针值和delete
1.编译器不能分辨指针指向的是静态对象还是动态分配的对象。写程序时要注意。
2.编译器不能分辨一个指针指向的内存是否被释放掉了。多次delete会出错。
3.动态分配的const的对象值虽然不能改变,但是可以使用delete将其销毁。
4.delete指针之后,指针变为空悬指针,指向一块曾保留数据现在变为无效的内存的指针。应该将其置为nullptr
12.1.3 shared_ptr和new结合使用
对于一个shared_ptr
,如果不对其初始化,则其为一个空指针,不能将内置指针隐式转换为shared_ptr,必须使用直接初始化的形式。
一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针会delete其指向的对象。
不要混合使用普通指针和智能指针,因为智能指针会delete其指向的对象,在程序中不知道什么时候可能智能指针就将对象delete掉了,再使用普通指针访问对象就会出错!!!
12.1.4 智能指针与异常
发生异常时,智能指针能保证内存被释放,但是内置指针管理的不行。
12.1.5 unique_ptr
- 没有类似
make_shared
的标准库函数返回一个unique_ptr
。 - 初始化
unique_ptr
必须使用直接初始化形式,如:
unique_ptr<double> p1;
unique_ptr<int> p2(new int(1024));
unique_ptr
不支持普通的拷贝或赋值操作,当然也有从函数中返回这种特殊情况
unique_ptr<string> p1(new string("dfhgah"));
unique_ptr<string> p2(p1);//错误, 不支持拷贝
unique_ptr<string> p3;
p3 = p1;//错误, 不支持赋值
unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));//作为返回值
}
unique_ptr<int> clone(int p){
unique_ptr<int> ret(new int(p));//局部对象
return ret;//作为返回值
}
-
使用
reset()
与release()
将指针的所有权从一个unique_ptr
转移到另一个。
-
release()
放弃对指针的控制,并返回一个指针,如果返回的指针没人用,内存将不会被释放。如:p2.release()
会丢失指针且其指向的内存不会被释放。 -
reset()
会释放指针指向的对象,u.reset(q)
会将u指向q,u原来指向的对象会被释放。
12.1.6 weak_ptr
weak_ptr
要用shared_ptr
或者weak_ptr
来初始化。weak_ptr
不增加引用计数。weak_ptr
访问对象需借助lock()
函数。weak_ptr
主要是为了解决shared_ptr
循环引用的问题。具体见weak_ptr浅析。
12.2 动态数组
12.2.1 new和数组
- 使用new分配数组时,得到的不是数组类型的对象,而是得到一个数组元素类型的指针。
- 不管是单个分配还是数组,都会默认初始化,C++11可以使用列表初始化。列表中的元素数量大于分配数量会抛出
bad_array_new_length
异常。 new
分配大小为0的数组,不会失败,而是返回一个合法的非空指针,但不能用它进行解引用。- 使用
delete
销毁数据时,总是先销毁最后一个,倒序销毁。