只能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。智能指针定义在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. java编译器优化和运行期优化

    概述    最近在看jvm优化,总结一下学习的相关知识 (一)javac编译器 编译过程 1.解析与填充符号表过程 1).词法.语法分析    词法分析将源代码的字符流转变为标记集合,单个字符是程序编 ...

  2. dynamic web module version

    Ser vlet 3十二月2009开发平台标准版6,6可插性,易于开发,异步ser vlet,安全,文件上传 Ser vlet 2.5九月2005开发平台标准版5,5需要平台标准版5,支持注释 Ser ...

  3. CF526F Pudding Monsters

    CF526F Pudding Monsters 题目大意:给出一个\(n* n\)的棋盘,其中有\(n\)个格子包含棋子. 每行每列恰有一个棋子. 求\(k*k\)的恰好包含\(k\)枚棋子的子矩形个 ...

  4. dotnet core 黑科技·String

    本文来告诉大家 dotnet core 里面使用的黑科技,如何提高String.IndexOf(char)的性能 在Performance Improvements in .NET Core有说道哪些 ...

  5. js算法(1)

    数组排序 arr.sort(function compare(a,b){return b.value-a.value}); json 排序 $.getJSON('URl',function(data) ...

  6. IdentityServer4 sign-in

    原文地址 Sign-in IdentityServer 代表 user 分配token之前,user必须登录IdentityServer Cookie authentication 使用 cookie ...

  7. 由“Sysnative”引发的思考

    在64位的Windows系统中,有个非常神秘的文件夹“Sysnative”,你无法通过Explorer去访问它,甚至你都无法找到它,但它却扮演了一个非常重要的角色.下面我们就来聊聊它. 32位和64位 ...

  8. jQuery的使用及关于框架造型 专题

    Introduction 正如jQuery所宣称的一样,Write Less, Do More.很多时候我们喜欢用它来解决问题.但增加一个库必然意味着更大的网络负担,意味着更高的页面初始载入时间.并且 ...

  9. The Struts dispatcher cannot be found异常的解决方法

    系统错误:HTTP Status 500 异常信息:The Struts dispatcher cannot be found.  This is usually caused by using St ...

  10. Struts2 注释类型

    Struts 2 应用程序可以使用Java5注释作为替代XML和Java属性配置.这里是清单的不同的类别有关的最重要的注解: 命名空间注释(动作注释): @ Namespace注释允许在Action类 ...