动态内存
动态内存与智能指针
- C++中使用
new
和delete
进行动态内存的管理,但是有时候难以确保内存分配或释放的准确性。针对这个问题,标准库中提供了智能指针,可以自动释放所指的对象,shared_ptr
允许多个指针指向同一个对象。
shared_ptr
- 进行拷贝或者赋值时,每个
shared_ptr
会记录有多少个其他的shared_ptr
指向相同的对象,可以认为每个shared_ptr
都有一个关联计数器,通常称为引用计数,拷贝一个shared_ptr
时,引用计数会递增,一个shared_ptr
对象被销毁时,其对应的引用计数会递减。 shared_ptr
自动销毁所管理的对象是通过对象的析构函数来实现的。shared_ptr
也会自动释放相关联的内存。- 使用动态内存的一个常见原因是:允许多个对象共享相同的状态。平时常用的容器是通过拷贝的方式生成新的容器,如果允许多个容器可以共享相同的元素,则可以节省拷贝成本,这可以使用智能指针实现。
code
void test() { // 使用智能指针的方式,sps会首先指向一个默认初始化的string shared_ptr<string> sps; if (sps && sps->empty()) { *sps = "233"; cout << *sps << endl; } shared_ptr<int> p1 = make_shared<int>(42); shared_ptr<string> p2 = make_shared<string>(10, 'A'); // make_shared可以提供特定类型的初始化参数 shared_ptr<int> p3 = make_shared<int>(); cout << *p1 << endl; cout << *p2 << endl; cout << *p3 << endl; shared_ptr<vector<string>> pstr1 = make_shared < vector<string> >(3, "2333"); cout << pstr1.use_count() << endl; // 1次 shared_ptr<vector<string>> pstr2 = pstr1; pstr2->push_back("test"); // 指向同一位置,所以修改pstr2,pstr1也会改变 for_each(pstr1->begin(), pstr1->end(), [](const string& str){ cout << str << endl; }); cout << pstr1.use_count() << endl; // 2次 }
直接管理内存
- 使用new和delete可以直接管理内存
- new用于动态分配内存,delete用于释放内存空间
- 在new时,如果申请内存空间失败,会报出
bad_alloc
的异常,因此可以使用nothrow
,在申请内存空间失败时,返回空指针,而不会报出异常。 - new与delete需要成对使用,否则申请的内存空间无法释放。
- 使用new与delete管理内存有几个主要的问题
- 常常忘记delete内存
- 使用已经释放掉的对象
- 同一块内存被释放两次
- 鉴于上面的问题,建议在使用中使用智能指针。
code
void test() { string s; // string *ps; // *ps = "233"; // 这种方法需要首先对指针进行赋值,否则会报错 string *p1 = new string; *p1 = "test"; cout << *p1 << endl; delete p1; // 必须手动释放内存 int *p2 = new int(42); delete p2; // 如果出现了内存不够导致无法分配内存的情况,可以使用nothrow,如果分配失败,则会返回空指针 int *p3 = new (nothrow) int(50); cout << *p3 << endl; const string* p4 = new string("hello"); // *p4 = "ssss"; // 因为是const对象,无法修改其值,会报错 delete p4; }
shared_ptr与new结合
如果不对智能指针进行初始化,则它会被初始化为1个空指针,可以通过直接初始化的方式将一个内置指针转换为智能指针,注意:无法通过隐士转换的方法实现这样的初始化
code
void test() { // shared_ptr<int> p1 = new int(4); // 无法通过这样的方法实现隐式转换 shared_ptr<int> p2( new int(42) ); // 可以通过直接初始化的方式实现智能指针的初始化 cout << *p2 << endl; }
get
- get返回一个内置指针,指向智能指针管理的对象。
- 不建议使用这个属性,因为有可能会使得内存空间管理发生混乱。
code
void another(int *p) { shared_ptr<int> ret( p ); } void test() { shared_ptr<int> p1( new int(5) ); another(p1.get() ); // p1所对应的内存空间会在another函数中被释放,下面的操作在运行的时候会出现错误 //int num = *p1; //cout << num << endl; }
自定义智能指针释放时的操作
- 智能指针可以自己调用析构函数进行资源释放,我们也可以指定这个智能指针对象的删除操作,需要传入一个指针对象,类型即数据类型。即使是异常,也会调用自定义的函数进行资源释放
code
void freed(int *p) { delete p; cout << "p is being deleted" << endl; } void test() { // 自定义析构的函数 shared_ptr<int> p1( new int(5), freed ); // throw out_of_range("2333"); // 即使是出现异常,也可以调用freed进行资源释放 }
unique_ptr
- 之前的shared_ptr是通过引用计数的方法,允许多个变量使用同一块地址,
unique_ptr
只能指向一个给定的对象,可以使用new的方式进行直接初始化 - unique_ptr不支持拷贝与赋值
- 可以通过release与reset的方法将指针的所有权从一个
unique_ptr
转化到另一个unique_ptr
。 code
void test() { unique_ptr<int> p1(new int(4)); cout << *p1 << endl; //不支持拷贝与赋值 // unique_ptr<int> p2( p1 ); // unique_ptr<int> p2 = p1; unique_ptr<int> p2( p1.release() ); // release:p1放弃对指针的控制权,将p1置为空,同时返回该指针 cout << (p1 == NULL ? "p1 is NULL" : "p1 is not NULL") << endl; cout << *p2 << endl; p2.reset(); // 释放了p2所指的内存,同时将p2置为空 cout << (p2 == NULL ? "p2 is NULL" : "p2 is not NULL") << endl; }
删除器
- 默认情况下,
unique_ptr
与shared_ptr
都是使用delete释放它所指的对象;同时也可以向unique_ptr
中传入删除器,但是使用方法和shared_ptr
有所差别,他需要传入2个类型模板 code
void freed(int *p) { delete p; cout << "p is being deleted" << endl; } void test() { // 可以使用decltype进行函数指针的声明,方便一些 //unique_ptr<int, decltype(freed)*> p1( new int(4), freed ); unique_ptr<int, void(*)(int*)> p1(new int(4), freed); cout << *p1 << endl; }
weak_ptr
- 很少用到,这里不做介绍
动态数组
- new分配的对象不管是单个分配的还是数组中的,都是默认初始化的,可以在定义之后,在后面加上一对空括号,实现值初始化
- 可以动态分配一个大小为0的数组,这是合法的,但是该数组无法进行解引用的操作。
- 释放数组可以用
delete [] p
的方式,如果是单个值,用delete p
即可。 标准库中提供了可以管理new分配的数组的
unique_ptr
版本,当智能指针释放时,会调用delete [] p
进行数组资源释放,但是shared_ptr
没有这样的操作,如果需要其管理数组,我们需要自定义删除器void test() { int *p1 = new int[10]; //没有初始化 int *p2 = new int[10](); // 初始化值为0 int *p3 = new int[10]{1,2,3,4}; cout << *p1 << ", " << *p2 << endl; for (int i = 0; i < 10; i++) cout << p3[i] << " "; cout << endl; int *p4 = new int[0](); // 初始化大小为0没有问题 //cout << *p4 << endl; //在这里解引用会出现意想不到的结果 delete[] p1; delete[] p2; delete[] p3; delete[] p4; cout << "unique_ptr\n"; unique_ptr<int[]> p5(new int[10]{1,2,3,4,5,6}); for (int i = 0; i < 10; i++) cout << p5[i] << " "; cout << endl; cout << "shared_ptr\n"; shared_ptr<int> p6(new int[10]{8, 7, 6, 5, 4}, [](int *p){ cout << "user defined delete func for shared_ptr" << endl; delete[] p; }); for (int i = 0; i < 10; i++) cout << *(p6.get()+i) << " "; // 没有实现上面的下标引用访问的方式 cout << endl; }