导读

STL提供四种智能指针:auto_ptr、unique_ptr、shared_ptr和weak_ptr。其中auto_ptr是C++98提供的解决方案,C++11以后均已摒弃。所有代码在gcc 8.1上编译。

设计思想

将基本类型指针封装为类对象指针模板,并在析构函数中编写delete语句删除指针指向的内存空间,并且每个智能指针类都有一个explicit构造函数。比如auto_ptr的类模板原型为:

template<typename _Tp>
class auto_ptr
{
private:
_Tp* _M_ptr;
public:
explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
~auto_ptr() { delete _M_ptr; }
}

auto_ptr

初始化方法:

1、构造函数拷

  • 将已经存在的指向动态内存的普通指针作为参数来构造。
int* p = new int(10);
auto_ptr<int> auto_p(p);
  • 直接构造。
auto_ptr<int> auto_p(new int(10));

2、拷贝构造

  • 利用已经存在的指针构造新的auto_ptr指针。因为动态内存只能由一个auto_ptr指针独享,所以在拷贝构造或赋值时会发生拥有权转移。在拷贝构造过程中,auto_p1失去对字符串内存的所有权,由auto_p2获得所有权。对象销毁时须由auto_p2负责内存的自动销毁。
auto_ptr< string > auto_p1 ( new string( "p1" ) );
auto_ptr< string > auto_p2( auto_p1 );

3、赋值

  • 利用已经存在的auto_ptr指针来构造新的auto_ptr指针。在赋值之前,由auto_p1指向的对象被销毁。赋值后auto_p1拥有int型对象所有权,值为20。auto_p2不在指向该对象(即成为空指针)。
auto_ptr< int > auto_p1( new int( 10 ) );
auto_ptr< int > auto_p2( new int( 20 ) );
auto_p1 = auto_p2;

注意事项:

  • 因为auto_ptr的所有权独有,所以防止两个auto_ptr对象指向同一块内存。这样会导致程序潜在的内存崩溃,这也是摒弃auto_ptr的原因。
#include <iostream>
#include <string>
#include <memory> using namespace std; int main() {
auto_ptr<string> datas[3] =
{
auto_ptr<string>(new string("data1")),
auto_ptr<string>(new string("data2")),
auto_ptr<string>(new string("data3"))
};
auto_ptr<string> p;
p=datas[1]; //datas[1]将所有权转给p,此时datas[1]不再指向"data2"字符串而变成空指针。
for(int i=0; i<3; i++)
{
cout<<*datas[i]<<endl; //i=1时,程序崩溃,使用shared_ptr、unique_ptr可以避免程序本刊问题
}
cout<<*p<<endl;
return 0;
}
  • 警惕auto_ptr智能指针作为参数。按值传递时,在函数调用过程中函数的作用域中会产生一个局部对象来接收传入的auto_ptr,此时传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。按引用或指针传递时,不存在拷贝构造过程,但不能保证在函数内部对传入的auto_ptr做什么操作而产生auto_ptr指针被删除的问题。
void fun(auto_ptr<string> ap_)
{
cout<<"print in fun:"<<*ap_<<endl;
}
int main() {
auto_ptr<string> p (new string("data1"));
fun(p);//函数执行完后,"data1"所在的对象被删除。
cout<<*p<<endl;//引用空指针,程序崩溃。
return 0;
}

unique_ptr

unique_ptr是C++11提供的用于防止内存泄漏的智能指针,它独享被管理对象的所有权。unique_ptr对象封装原生指针,负责其生命周期。unique_ptr与auto_ptr相比,在编译环节就保证了unique_ptr的安全问题,因而比auto_ptr智能指针更安全。具体如下语句所示:

auto_ptr<string> p1(new string("data1"));
auto_ptr<string> p2;
p2 = p1; //编译器认为合法,但后续对p1继续使用,程序运行时出错,因为p1不再指向有效数据。 unique_ptr<string> p3(new string("data2"));
unique_ptr<string> p4;
p4 = p3; // 编译器认为非法,避免p3不再指向有效数据问题。

将unique_ptr指针赋给另一个unique_ptr指针时不会留下危险的悬挂指针。但当将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做。如果要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。

unique_ptr<string> demo(const char* s)
{
unique_ptr<string> temp(new string(s));
return temp;
}
int main() { /**
* demo()返回临时指针时,p接管了原本归返回的unique_ptr所拥有的对象,而返回的unique_ptr被销毁,
* 即unique_ptr没有机会访问无效数据。
* */
unique_ptr<string> p = demo("demo");
cout<<*p<<endl; unique_ptr<string> p1(new string ("p1"));
unique_ptr<string> p2;
//p2 = p1; // 编译器不允许
unique_ptr<string> p3;
p3 = unique_ptr<string>(new string ("3")); // 编译器允许 p2 = move(p); //p转让所有权,变成空指针。
if(p2 != nullptr){
cout<<*p2<<endl;
}
return 0;
}

shared_ptr

与unique_ptr独占所指向对象情况相反,shared_ptr基于"引用计数"模型实现,允许多个shared_ptr智能指针指向同一个动态对象,并维护一个共享引用计数器,当拷贝或赋值一个shared_ptr时计数器加一,被销毁时(如一个局部shared_ptr指针离开其作用域)计数器递减,当计数器变为0时,shared_ptr自动释放自己所管理的对象。

注意事项:

  • make_shared是最安全的指针初始化方式。
string s = "p";
shared_ptr<string> p = make_shared<string>(s);
  • 裸指针初始化多个share_ptr。
string s = "p";
shared_ptr<string> p = make_shared<string>(s);
/*不要这样做*/
shared_ptr<string> p1 = make_shared<string>(s);
cout<<*p<<endl;
cout<<*p1<<endl; auto *p0 = new std::string("hello");
std::shared_ptr<std::string> p2(p0);
/*不要这样做*/
std::shared_ptr<std::string> p3(p0);
cout<<*p2<<endl;
cout<<*p3<<endl;
  • 不用get()获取裸指针,然后去初始化另外一个shared_ptr,或者delete get返回的指针。
auto p = std::make_shared<std::string>("hi");
std::string *p0 = p.get();
std::shared_ptr<std::string> p2(p0);//非法
delete p0;//非法
  • 如果对象不是new分配的,传递删除器。
std::shared_ptr<T> make_shared_array(size_t size) {
return std::shared_ptr<T>(new T[size], std::default_delete<T[]>());
}
int main(){
//lambda
std::shared_ptr<int> p(new int[10], [](int* p){delete [] p;});
//指定默认删除器
std::shared_ptr<int> p1(new int[10], std::default_delete<int[]>());
//自定义泛型方法
std::shared_ptr<char> p2 = make_shared_array<char>(10);
}
  • 循环引用导致内存泄漏。例如,假设我设计一个二叉树,并在其中包含一个指向左右子节点的指针。
class Node {
int value;
public:
shared_ptr<Node> leftPtr;
shared_ptr<Node> rightPtr;
Node(int val) : value(val) {
cout << "Constructor" << endl;
}
~Node() {
cout << "Destructor" << endl;
}
}; int main(){
shared_ptr<Node> ptr = make_shared<Node>(4);
ptr->leftPtr = make_shared<Node>(2);
ptr->rightPtr = make_shared<Node>(5);
cout<<ptr.use_count()<<endl;
cout<<ptr->leftPtr.use_count()<<endl;
cout<<ptr->rightPtr.use_count()<<endl;
return 0;
}
/*
运行正常,调用3次构造函数和3次析构函数,输出如下:
Constructor
Constructor
Constructor
1
1
1
Destructor
Destructor
Destructor
*/

如果给每个节点添加一个父节点时,则导致share_ptr内测泄漏。

class Node {
int value;
public:
shared_ptr<Node> leftPtr;
shared_ptr<Node> rightPtr;
shared_ptr<Node> parentPtr;
Node(int val) : value(val) {
cout << "Constructor" << endl;
}
~Node() {
cout << "Destructor" << endl;
}
}; int main(){
shared_ptr<Node> ptr = make_shared<Node>(4);
ptr->leftPtr = std::make_shared<Node>(2);
ptr->leftPtr->parentPtr = ptr;
ptr->rightPtr = std::make_shared<Node>(5);
ptr->rightPtr->parentPtr = ptr;
cout<<ptr.use_count()<<endl;
cout<<ptr->leftPtr.use_count()<<endl;
cout<<ptr->rightPtr.use_count()<<endl;
return 0;
}
/*
运行非正常,调用3次构造函数,一直运行到程序结束也没调用析构函数,造成内存泄漏,输出如下:
Constructor
Constructor
Constructor
3
1
1
*/

weak_ptr

weak_ptr是一个伴随类,弱引用,weak_ptr允许共享,但不拥有对象,它的对象由shared_ptr创建。用于解决shared_ptr循环引用导致的内存泄漏问题。用法如下:

shared_ptr<int> ptr = make_shared<int>(4);
weak_ptr<int> weakPtr(ptr);

对于weak_ptr指针,不能直接用运算符*和->来访问关联的内存。必须先通过调用weak_ptr指针对象的lock函数来创建一个shared_ptr指针才能使用。

shared_ptr<int> ptr = make_shared<int>(4);
weak_ptr<int> weakPtr(ptr);
shared_ptr<int> ptr2 = weakPtr.lock();
if (ptr2)
{
cout << (*ptr2) << endl;
}

使用weak_ptr改进二叉树示例,运行正常。

class Node {
int value;
public:
shared_ptr<Node> leftPtr;
shared_ptr<Node> rightPtr;
//把shared_ptr改为weak_ptr;
weak_ptr<Node> parentPtr;
Node(int val) : value(val) {
cout << "Constructor" << endl;
}
~Node() {
cout << "Destructor" << endl;
}
};
int main(){
shared_ptr<Node> ptr = make_shared<Node>(4);
ptr->leftPtr = std::make_shared<Node>(2);
ptr->leftPtr->parentPtr = ptr;
ptr->rightPtr = std::make_shared<Node>(5);
ptr->rightPtr->parentPtr = ptr;
cout<<ptr.use_count()<<endl;
cout<<ptr->leftPtr.use_count()<<endl;
cout<<ptr->rightPtr.use_count()<<endl;
}
/*
运行正常,调用3次构造函数和3次析构函数,输出如下:
Constructor
Constructor
Constructor
1
1
1
Destructor
Destructor
Destructor
*/

C++智能指针使用说明的更多相关文章

  1. c++智能指针使用笔记

    1. c++智能指针中,c++的memory文件中,有auto_ptr等各种关于智能指针的东西,shared_ptr,weak_ptr在C++11中已经成为标准. 也看了ogs的智能指针,每次引用起来 ...

  2. 第21课 shared_ptr共享型智能指针

    一. shared_ptr的基本用法 (一)与unique_ptr的比较 比较 shared_ptr unique_ptr 备注 初始化 ①shared_ptr<T> sp; sp.res ...

  3. enote笔记法使用范例(2)——指针(1)智能指针

    要知道什么是智能指针,首先了解什么称为 “资源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“资源分配即初始化” 在&l ...

  4. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  5. C++智能指针

    引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...

  6. EC笔记:第三部分:17、使用独立的语句将newed对象放入智能指针

    一般的智能指针都是通过一个普通指针来初始化,所以很容易写出以下的代码: #include <iostream> using namespace std; int func1(){ //返回 ...

  7. 智能指针shared_ptr的用法

    为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...

  8. 智能指针unique_ptr的用法

    unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误用法: std::unique_pt ...

  9. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

随机推荐

  1. 【剑指Offer】从上往下打印二叉树 解题报告(Python)

    [剑指Offer]从上往下打印二叉树 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interviews ...

  2. Misaki's Kiss again(hdu5175)

    Misaki's Kiss again Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Othe ...

  3. 1122 机器人走方格 V4

    1122 机器人走方格 V4 基准时间限制:1 秒 空间限制:131072 KB  四个机器人a b c d,在2 * 2的方格里,一开始四个机器人分别站在4个格子上,每一步机器人可以往临近的一个格子 ...

  4. 写了个适用于vscode的minio图床客户端插件

    缘起 自己搭建minio做我的个人博客图床好一段时间了, 一直用minio自带的web管理后台来上传图片, 它的界面长下面这个样子 上传完后, 需要点下文件列表里刚刚传上去的文件的分享按钮 然后会出来 ...

  5. Chapter 9 Measurement Bias

    目录 9.1 Measurement Error The structure of measurement error 9.3 Mismeasured confounders 9.4 Intentio ...

  6. InfoGAN

    目录 概 主要内容 Chen X., Duan Y., Houthooft R., Schulman J., Sutskever I., Abbeel P. InfoGAN: Interpretabl ...

  7. ImageNet2017文件下载

    ImageNet2017文件下载 文件说明 imagenet_object_localization.tar.gz包含训练集和验证集的图像数据和地面实况,以及测试集的图像数据. 图像注释以PASCAL ...

  8. Java初学者作业——编写Java程序,输入一个数字,实现该数字阶乘的计算。

    返回本章节 返回作业目录 需求说明: 编写Java程序,输入一个数字,实现该数字阶乘的计算.一个数字的阶乘是所有小于及等于该数的正整数的积,自然数n的阶乘写作n! .例如,5的阶乘等于1*2*3*4* ...

  9. iis站点下发布多个vue项目

    记录一下iis上某个域名下发布多个vue项目的过程,主要分为webpack打包前的配置和iis重定向的配置. vue打包配置: 1.在webpack 配置文件中(以vue.config.js为例),指 ...

  10. wget 工具使用

    wget http://xxxx 下载文件到当前文件夹 wget -V 显示wget的版本信息并退出 wget -v 打印详细信息 默认 wget -nv 关闭详细输出,但不进入安静模式 wget - ...