只能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。智能指针定义在memory头文件中。

1. auto_ptr(C++11已经舍弃)

由new expression获得的对象,在auto_ptr对象销毁时,他所管理的对象也会自动被delete掉。

auto_ptr<string> p1(new string("This is a string"));
auto_ptr<string> p2;
p2 = p1;

上述语句中,如果p1和p2是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次。

要避免这种问题,方法有多种:

  1. 定义赋值运算符,使之执行深拷贝。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
  2. 建立所有权概念。对于特定的对象,只能有一个指针可拥有,这样所拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略。
  3. 跟踪引用对象的智能指针数。这称为引用计数。例如:赋值时,计数加1,指针过期时,计数减1。当减为0时才调用delete。这就是sheared_ptr采用的策略。

为什么要弃用auto_ptr?

#include<memory>
#include<iostream>
using namespace std;
int main()
{
auto_ptr<string> p1(new string("This is a string"));
auto_ptr<string> p2 = p1; //p1将所有权转让给p2,此时p1不再引用该字符串从而变成空指针。
cout << *p1 << endl; //报错,此时p1已经没有所指向的内存的所有权。
cout << *p2 << endl;
}
  1. 使用shared_ptr时运行正常,因为shared_ptr采用引用计数,p1和p2都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。
  2. 使用unique_ptr时编译出错,与auto_ptr不同的是,使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译时出现错误。

舍弃auto_ptr的原因:避免因潜在的内存问题导致程序崩溃

2. unique_ptr(替换auto_ptr)

unique_ptr比auto_ptr更加安全,因为auto_ptr有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr禁止了拷贝语义,但提供了移动语义,即可以使用move()进行控制权限的转移。

unique_ptr<string> p1(new string("This is a string"));
unique_ptr<string> p2(p1); //编译出错,已禁止拷贝
unique_ptr<string> p3 = p1; //编译出错,已禁止拷贝
unique_ptr<string> p4 = std :: move(p1); //控制权限转移 auto_ptr<string> p1(new string("This is a string"));
auto_ptr<string> p2(p1); //编译通过,运行出错
auto_ptr<string> p3 = p1; //编译通过,运行出错

如果unique_ptr是个临时右值,编译器允许拷贝语义。

unique_ptr<string> demo(string *s)
{
unique_ptr<string> temp(new string(s));
return temp;
} unique_ptr<string> ps;
ps = demo("This is a string");

demo()返回一个临时unique_ptr,然后ps接管了临时对象所管理的资源,而返回时临时的unique_ptr被销毁,也就是说没有机会使用unique_ptr来访问无效的数据。相对于auto_ptr任何情况下都允许拷贝语义,这正是unique_ptr更加灵活的地方。

扩展auto_ptr不能完成的功能:

  1. unique_ptr可放在容器中,弥补了auto_ptr不能作为容器元素的缺点。
  2. 管理动态数组
  3. 自定义资源删除操作。

3. shared_ptr(引用计数型智能指针)

类似vector,智能指针也是模板,当我们创建一个智能指针时,必须提供额外的信息---指针可以指向的类型。

shared_ptr<string> p1;

默认初始化的智能指针中保存着一个空指针。

智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。

make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<string> p2 = make_shared<string>(10, '9');
auto p3 = make_shared<vector<string>>();

shared_ptr的拷贝和赋值

  1. 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。
  2. 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

shared_ptr自动销毁所管理的对象

  1. 它是通过另一个特殊的成员函数---析构函数(destructor)完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制该类型的对象销毁时做什么操作。
  2. 析构函数一般用来释放对象所分配的资源。
  3. shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

shared_ptr还会释放相关联的内存

  1. shared_ptr在无用之后仍然保留的一种可能情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保使用erase删除哪些不再需要的shared_ptr元素。

使用动态生存期的资源的类

  1. 程序不知道自己需要使用多少个对象(容器类)
  2. 程序不知道所需对象的准确类型
  3. 程序需要在多个对象间共享数据(如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面的销毁底层数据。)

4. weak_ptr(辅助shared_ptr)

  1. weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
  2. 创建一个weak_ptr时,要用一个shared_ptr来初始化它
  3. 不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr.

5.shared_ptr循环引用

#include<iostream>
#include<memory>
using namespace std; class ListNode{
public:
int m_value;
shared_ptr<ListNode> prev;
shared_ptr<ListNode> next;
//构造函数
ListNode(int value):m_value(value){
cout << "constructor called!" <<endl;
}
//析构函数
~ListNode(){
cout << "destructor called!" <<endl;
}
}; void test(){
shared_ptr<ListNode> sp1 = make_shared<ListNode>(33);
shared_ptr<ListNode> sp2 = make_shared<ListNode>(44);
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1 -> next = sp2;
sp2 -> prev = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
} int main(){
test();
return 0;
}
//运行结果:
constructor called!
constructor called!
1
1
2
2

构造的sp1和sp2在出它们的作用域(即test())时,都没有被析构,从而造成了内存泄漏。

6.weak_ptr是如何解决循环引用的?

#include<iostream>
#include<memory>
using namespace std; class ListNode{
public:
int m_value;
weak_ptr<ListNode> prev;
weak_ptr<ListNode> next;
//构造函数
ListNode(int value):m_value(value){
cout << "constructor called!" <<endl;
}
//析构函数
~ListNode(){
cout << "destructor called!" <<endl;
}
}; void test(){
shared_ptr<ListNode> sp1 = make_shared<ListNode>(33);
shared_ptr<ListNode> sp2 = make_shared<ListNode>(44);
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1 -> next = sp2;
sp2 -> prev = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
} int main(){
test();
return 0;
}
//运行结果:
constructor called!
constructor called!
1
1
1
1
destructor called!
destructor called!

弱引用不修改对象的引用计数;弱引用能检测对象是否已经被释放。

只要把循环引用的一方使用弱引用,即可解除循环引用。

C++中的四个智能指针的更多相关文章

  1. c++中的四种智能指针

    c++中的四种智能指针 写惯了python,golang再来写c++总觉得头大,很大一个原因就是他没有一个GC机制. 不过c++中提供了智能指针,也不是不能用,李姐万岁! auto_ptr, shar ...

  2. 转载:STL四种智能指针

    转载至:https://blog.csdn.net/K346K346/article/details/81478223 STL一共给我们提供了四种智能指针: auto_ptr.unique_ptr.s ...

  3. stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结

    stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结 1. auto_ptrauto_ptr主要是用来解决资源自动释放的问题,比如如下代码:voi ...

  4. c++ 中的8种智能指针[转]

    一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 ...

  5. C++面试题(四)——智能指针的原理和实现

    C++面试题(一).(二)和(三)都搞定的话,恭喜你来到这里,这基本就是c++面试题的最后一波了.     1,你知道智能指针吗?智能指针的原理.     2,常用的智能指针.     3,智能指针的 ...

  6. 聊聊 C++ 中的几种智能指针 (下)

    一:背景 上一篇我们聊到了C++ 的 auto_ptr ,有朋友说已经在 C++ 17 中被弃用了,感谢朋友提醒,今天我们来聊一下 C++ 11 中引入的几个智能指针. unique_ptr shar ...

  7. 聊聊 C++ 中的几种智能指针 (上)

    一:背景 我们知道 C++ 是手工管理内存的分配和释放,对应的操作符就是 new/delete 和 new[] / delete[], 这给了程序员极大的自由度也给了我们极高的门槛,弄不好就得内存泄露 ...

  8. STL 智能指针

    转自: https://blog.csdn.net/k346k346/article/details/81478223 STL一共给我们提供了四种智能指针:auto_ptr.unique_ptr.sh ...

  9. Effective Modern C++:04智能指针

    裸指针有着诸多缺点:裸指针的声明中看不出它指向的是单个对象还是数组:裸指针的声明中也无法看出使用完它指向的对象后是否需要删除,也就是声明中看不出裸指针是否拥有其指向的对象:即使知道要析构裸指针指向的对 ...

随机推荐

  1. 51nod 1282 时钟

    1282 时钟  题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 有N个时钟,每个时钟有M个指针,P个刻度.时钟是圆形的,P个刻度均分 ...

  2. 2019-9-24-dotnet-remoting-抛出异常

    title author date CreateTime categories dotnet remoting 抛出异常 lindexi 2019-09-24 15:39:50 +0800 2018- ...

  3. Linux 内核usb_bulk_msg 接口

    usb_bulk_msg 创建一个 USB 块 urb 并且发送它到特定的设备, 接着在返回到调用者之 前等待完成. 它定义为: int usb_bulk_msg(struct usb_device ...

  4. Oracle如何分组排序并产生序号

    SELECT C.ORG_SHORTNAME, B.USER_NAME, ROW_NUMBER () OVER ( PARTITION BY B.ORG_ID ORDER BY A.TOTAL_SCO ...

  5. Python8_关于编码解码和utf-8

    关于编码:ASCII码是早期的编码规范,只能表示128个字符.7位二进制数表示 扩展ASCII码,由于ASCII码不够用,ASCII表扩充到256个符号,不同的国家有不同的标准:8位二进制数 Unic ...

  6. Java并发编程系列-(8) JMM和底层实现原理

    8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...

  7. Kafka学习笔记4--Kafka消费者的客户端(PHP)开发

    一.准备工作 虽然 Kafka 是用 Java/Scala 语言编写的,但这不妨碍它对多语言的支持.可以在 Kafka 官网的 CLIENTS 查看 Kafka 支持的语言,其中包括 C/C++.Py ...

  8. The first day of Crawler learning

    使用BeautifulSoup解析网页 Soup = BeautifulSoup(urlopen(html),'lxml') Soup为汤,html为食材,lxml为菜谱 from bs4 impor ...

  9. 【转】线性插值(Linear Interpolation)基本原理

    转:https://blog.csdn.net/u010312937/article/details/82055431 今天在阅读大牛代码的时候,发现了Linear Interpolation一次,百 ...

  10. GC 为什么要挂起用户线程? 什么愁什么怨?

    GC 为什么要挂起用户线程? 什么愁什么怨? 前言 JVM 系列文章的第一篇.敬请期待后续. 故障描述 某年某月某日 上午,线上发生故障,经过排查,发现某核心服务 Dubbo 接口超时. 故障根源 查 ...