C++智能指针使用说明
导读
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++智能指针使用说明的更多相关文章
- c++智能指针使用笔记
1. c++智能指针中,c++的memory文件中,有auto_ptr等各种关于智能指针的东西,shared_ptr,weak_ptr在C++11中已经成为标准. 也看了ogs的智能指针,每次引用起来 ...
- 第21课 shared_ptr共享型智能指针
一. shared_ptr的基本用法 (一)与unique_ptr的比较 比较 shared_ptr unique_ptr 备注 初始化 ①shared_ptr<T> sp; sp.res ...
- enote笔记法使用范例(2)——指针(1)智能指针
要知道什么是智能指针,首先了解什么称为 “资源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“资源分配即初始化” 在&l ...
- C++11 shared_ptr 智能指针 的使用,避免内存泄露
多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...
- C++智能指针
引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...
- EC笔记:第三部分:17、使用独立的语句将newed对象放入智能指针
一般的智能指针都是通过一个普通指针来初始化,所以很容易写出以下的代码: #include <iostream> using namespace std; int func1(){ //返回 ...
- 智能指针shared_ptr的用法
为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...
- 智能指针unique_ptr的用法
unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误用法: std::unique_pt ...
- 基于C/S架构的3D对战网络游戏C++框架_05搭建系统开发环境与Boost智能指针、内存池初步了解
本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...
随机推荐
- 第二十五个知识点:使用特殊的素数定义$GF(p)$和$GF(2^n)$的方法。
第二十五个知识点:使用特殊的素数定义\(GF(p)\)和\(GF(2^n)\)的方法. 在我们之前看到的博客中,当实现密码学方案时,一个最频繁调用的操作就是模运算.不幸的是,尽管模块化的使用非常广泛, ...
- oracle函数listagg使用
作用 可以实现将多列记录聚合为一列记录,实现数据的压缩 语法结构 listagg(measure_expr,delimiter) within group ( order by order_by_cl ...
- PowerDotNet平台化软件架构设计与实现系列(11):日志平台
所有后端应用几乎都会记录日志,日志系统可以统一抽象出来提供服务. 最近被Log4j2的安全漏洞刷屏了,作为开发人员的我只能咩哈哈几次表示日志处理太难了,只有折腾过的人才知道这里面的艰辛啊. 在实现Po ...
- <数据结构>XDOJ323.判断有向图中是否有环
问题与解答 问题描述 判断有向图中是否有环. 输入格式 输入数据第一行是一个正整数,表示n个有向图,其余数据分成n组,每组第一个为一个整数,表示图中的顶点个数n,顶点数不超过100,之后为有向图的邻接 ...
- SpringBoot集成log4j,解决log4j.properties不生效问题
Spring Boot集成log4j其实比较简单,maven的话,在xml中增加log4j依赖就行 <dependency> <groupId>org.springframew ...
- 【】Elasticsearch客户端API使用Demo
Elasticsearch客户端API使用Demo, 转载自官方文档, 以索引雇员文档为示例, 在命令行使用curl演示了一系列的Restful API操作. 1.索引雇员文档 第一个业务需求就是存储 ...
- mysql语句1-创建库和表
一.DDL数据定义语言 就是对书库内部的对象进行创建.删除.修改等操作的语言. 关键字:create drop alter 1.连接数据库 mysql -u用户名 -p -h指定主机(不指定默认是 ...
- 初识python: 装饰器
定义: 本质是函数,功能是"装饰"其它函数,即为其他函数添加附加功能原则: 1.不能修改被装饰函数的源代码: 2.不能修改被装饰函数的调用方式实现装饰器知识储备: 1.函数即&qu ...
- 不用下载Axure RP Extension for Chrome插件查看原型文件的方法
Axure RP Extension for Chrome是一款谷歌插件,主要可以用来查看原型文件.以前安装插件的时候总是找半天资源,很麻烦,最近发现了一种新方法可以不用下载插件资源.其实在原型文件中 ...
- [Docker] 制作并运行 Nginx 镜像
环境 操作系统(cat /etc/redhat-release):CentOS Linux release 7.6.1810 (Core) Docker:18.09.6 文件 Dockerfile F ...