文章目录
智能指针
智能指针与常规指针的重要区别在于前者负责自动释放所指向的对象;
智能指针也是一种模板,需要提供可以指向的类型;
为了更容易的管理动态内存,新的标准库提供了两种智能指针类型:
shared_ptr允许多个指针指向同一个对象;
unique_ptr则“独占”所指向的对象。
标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。以上三种都定义在头文件memory中。
shared_ptr类
shared_ptr<string> p; //可以指向string的智能指针,默认初始化为空指针
//判断p是否为空,且p是否指向空字符串
if(p && p->empty()){
*p = "hi"; //如果指向空字符串,解指针,赋新值
}
make_shared函数
调用该函数在动态内存中分配一个对象空间并初始化它,返回指向此对象的shared_ptr。
在调用make_shared函数时,需要指定想要创建的对象类型:
//p指向一个值为42的整型
auto p = make_shared<int>(42); //赋值操作,指向对象的指针只有p
auto q(p); //拷贝操作,指向对象的指针加1
当执行赋值或拷贝操作的时候,shared_ptr会记录下有多少个其他的shared_ptr指向同样的对象,这里可以认为每个shared_ptr都有一个关联的计数器,通常称之为“引用计数“,当拷贝一个shared_ptr时,计数器都会增加。当给shared_ptr赋予新值或者被销毁的时候,计数器就会递减,当计数器的值减为0时,他就会自动销毁自己管理的对象,释放相关联的内存。
使用了动态生存期的资源的类
程序使用动态内存一般出于以下三种原因:
1.程序不知道自己需要使用多少对象;
2.程序不知道使用对象的准确类型;
3.程序需要在多个对象间共享数据。
直接管理内存
可以使用关键字new来动态分配和初始化对象,但是new无法为其分配的对象命名,而是返回指向该对象的指针。
默认情况下,动态分配的对象时默认初始化的,这意味着内置类型或组合类型的对象的值将是未定义的,而类类型的对象将调用默认构造函数进行初始化。
因为编译器的遍历,可以使用auto来让初始化器来推断我们想要分配的对象的类型,但是只有当括号中仅有单一初始化器时才能使用:
auto p = new auto(obj); //p指向一个与obj类型相同的对象,用obj初始化
auto p2 = new auto(a,b,c); //错误
动态分配的const对象
用new分配const对象是合法的,其必须进行初始化,返回的指针也是const的。
const int* p = new const int(1024);
释放动态内存
为了防止内存耗尽,在动态内存使用完后,应该将它归还给系统,通过delete表达式来做到:销毁给定的指针所指向的对象,释放对应内存。
传递给delete的指针必须指向动态分配的内存或是一个空指针,释放并非new分配的内存,或将相同的指针值释放多次,其行为是未定义的。
通过new动态分配的内存在释放前都是存在的,所以需要自己手动来释放,这是与智能指针不同之处。
使用new和delete管理动态内存常见的三种错误:
1.忘记delete。这就是常说的内存泄露问题,查找内存泄露问题很困难,因为通常应用程序运行很长时间,真正耗尽内存后,才会被发现;
2.使用已经释放掉的内存。通过释放内存后将指针置为空,有时可以检测出这种情况;
3.同一块内存释放两次。当有两个指针指向相同的动态空间时可能会发生这种情况,delete第一次的时候,内存空间已经被归还了,再释放一次,自由空间就可能遭到破坏。
所以还是使用智能指针吧
使用自己的释放操作
如果对象自己有析构函数,那它在被释放的时候可以自动执行析构函数,但是如果它没有析构函数,就可能造成内存泄露。为避免这种情况,我们可以定义一个函数来代替delete,这个函数能对shared_ptr中保存的指针进行释放处理。在定义的时候将这个函数作为第二个参数传入,当该指针被销毁的时候,不会执行delete,而是调用自己定义的删除函数。
使用智能指针基本规范
1.不使用相同的内置指针值初始化(或reset)多个智能指针;
2.不delete、get()返回的指针;
3.不使用get()初始化或reset另一个智能指针;
4.如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,指针就变为无效的了;
5.如果你使用的智能指针管理的资源不是new分配的内存,记住传递给他一个删除器。
unique_ptr
一个unique_ptr“拥有”它所指向的对象,与shared_ptr不同的是在某个时刻只能有一个unique_ptr指向一个指定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。下表是unique_ptr独有的操作:
与shared_ptr不同的是,unique_ptr没有类似make_shared的函数返回指针。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针。初始unique_ptr必须使用直接初始化形式。因为unique_ptr拥有它指向的对象,所以它没有赋值或拷贝操作。
虽然不能拷贝和赋值,但是可以通过调用release和reset来将指针的所有权转交给另一个指针:
unique_ptr<string> p1(new string("this is a good thing"));
cout<<*p1<<endl; //输出this is a good thing
unique_ptr<string> p2(p1.release());
cout<<*p2<<endl; //输出this is a good thing
if(p1 == NULL){
cout<<"p1为空"<<endl; //执行这句
}
else{
cout<<"p1不为空"<<endl;
}
unique_ptr<string> p3(new string("this is a bad thing"));
p2.reset(p3.release()); //释放了原来的内存再指向另外的
cout<<*p2<<endl; // 输出this is a bad thing
if(p3 == NULL){
cout<<"p3为空"<<endl; //执行这句
}
else{
cout<<"p3不为空"<<endl;
}
值得注意的是release只是切断和对象的联系,并不释放对象的内存,需要我们手动释放:
auto p = p1.release();
delete(p);
传递unique_ptr参数和返回unique_ptr
不能拷贝unique_ptr规则有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr,比如:
unique_ptr<int> clone(int p){
return unique_ptr<int>(new int(p));
//或者
//unique_ptr<int> r(new int(p));
//return r;
}
weak_ptr
weak_ptr是一种不控制所指向对象生存周期的智能指针,它指向一个被shared_ptr所管理的对象,但是将它绑定到shared_ptr时并不会改变shared_ptr的引用计数,一旦最后一个shared_ptr被销毁,无论有没有weak_ptr指向对象,对象都会被释放。
创建一个weak_ptr时,要用一个shared_ptr来初始化它。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
对象可能不存在,所以不能使用weak_ptr直接访问对象,需要先调用lock检查对象是否还存在。
weak_ptr是一个弱引用指针,所以不会对资源的释放造成影响,也不会操作资源,通过lock返回的shared_ptr来操作资源,这在某些情况下比如循环引用造成的问题是很有帮助的,在https://segmentfault.com/q/1010000004078858看到解答,对理解很有帮助。