C++内存管理之shared_ptr
----------------------------------------shared_ptr---------------------------------------
引子
c++中动态内存的管理是通过new和delete来完成的,只要保证new和delete的配对使用,是没有问题的。但是有时候我们会忘记释放内存,甚至有时候我们根本就不知道什么时候释放内存。特别时在多个线程间共享数据时,更难判断内存该何使释放。这种情况下就机器容易产生引用非法内存的指针。
为了更容易(同时也更安全的管)的使用动态内存,新的标准库(C++11)提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似于常规指针。重要的区别是它负责自动释放所指向的对象。新标准提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则独占所指向的对象。标准库还定义了一个weak_ptr的伴随类,他是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
初始化 sahred_ptr
智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空:
#include <iostream> using namespace std; int main()
{
/*---------空指针------------*/
shared_ptr<string> p1;
if(!p1) //!默认初始化的智能指针中保存着一个空指针!并不是""空字符串
cout<<"p1==NULL"<<endl; /*---------初始化------------*/
shared_ptr<string> p2(new string);
if(p2&&p2->empty()){ //!需要注意的时empty时属于string的成员函数。
*p2="helloworld";
cout<<*p2<<endl;
}
// shared_ptr<int> pa = new int(1);//!error:不允许以暴露裸漏的指针进行赋值操作。 //一般的初始化方式
shared_ptr<string> pint(new string("normal usage!"));
cout<<*pint<<endl; //推荐的安全的初始化方式
shared_ptr<string> pint1 = make_shared<string>("safe uage!");
cout<<*pint1<<endl;
}
关于其它初始化智能指针的方法,如下;不推荐!在这里展示出来是希望极力避免不安全的使用范例。
/*不推荐*/
int * p = new int();
shared_ptr<int> pp(p);
cout<<*pp<<endl; /*意外的情况*/
// delete p; //!不小心把delete掉了。
// cout<<*pp<<endl;· //!pp也不再有效。
关于get()函数;
智能指针定义了一个名为get的函数,它返回一个内置指针,指向智能指针的管理的对象。此函数设置的初衷是当我们向不能使用智能指针的代码传递一个内置指针。使用get返回指针的代码不能delete此指针。
#include <iostream>
#include <memory>
using namespace std; void useShared_ptr(int *p)
{
cout<<*p<<endl;
} void delePointer(int *p)
{
delete p;
} int main(int argc, char *argv[])
{
shared_ptr<int> p1 = make_shared<int>();
// shared_ptr<int>p2(p1.get()); //!错误的用法:但是p1、p2各自保留了对一段内存的引用计数,其中有一个引用计数耗尽,资源也就释放了。
useShared_ptr(p1.get());
// delePointer(p1.get()); //!error:
return ;
}
再次声明:get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值!
关于mak_shared函数:
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回此对象的shared_ptr。与只能指针一样,make_shared也定义在头文件memory中。
#include <iostream>
using namespace std; int main()
{
shared_ptr<int> p3 = make_shared<int>();
cout<<*p3<<endl; shared_ptr<string> pstr = make_shared<string>("");
cout<<*pstr<<endl; shared_ptr<int> pint = make_shared<int>(); //!默认初始化为 0
cout<<*pint<<endl; auto pau = make_shared<string>("auto"); //!更简单,更常用的方式。
cout<<*pau<<endl;
}
使用make_shared用其参数来构造给定类型的对象;传递的参数必须能够与该类型的某个构造函数相匹配。
通常我们用auto来定义一个对象来保存make_shared的结果,这种方式更为简单。
shared_ptr的拷贝和赋值
当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象:
#include <iostream>
using namespace std; int main()
{
auto p = make_shared<int>(); //!p指向的对象只有p一个引用者。
cout<<p.use_count()<<endl;
auto q(p); //!p和q指向相同的对象,此对象有两个引用者。
cout<<p.use_count()<<endl;
return ;
}
shared_ptr作返回值:
#include <iostream>
using namespace std; shared_ptr<string> factory(const char* p){
return make_shared<string>(p);
} void use_factory(){
shared_ptr<string> p = factory("helloworld");
cout<<*p<<endl; //!离开作用域时,p引用的对象被销毁。 }
shared_ptr<string> return_share_ptr()
{
shared_ptr<string> p = factory("helloworld");
cout<<*p<<endl;
return p; //!返回p时,引用计数进行了递增操作。
} //!p离开了作用域,但他指向的内存不会被释放掉。
int main()
{
use_factory();
auto p = return_share_ptr();
cout<<p.use_count()<<endl;
}
引用计数:
可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr去初始化另一个shared_ptr;当我们给shared_ptr赋予一个新的值或者是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,他就会自动释放自己所管理的对象。
#include <iostream>
using namespace std; int main()
{
auto p = make_shared<int>(); //!指向的对象只有p一个引用者。
cout<<p.use_count()<<endl;
auto q = make_shared<int>();//!指向的对象只有q一个引用者。
cout<<q.use_count()<<endl; cout<<"---------afterAssin-----"<<endl;
p = q; //!p原来引用的对象经过赋值之后释放掉了,q引用的对象有了p和q两个引用。
cout<<*p<<"=="<<*q<<endl;
cout<<q.use_count()<<endl;
}
其他shared_ptr操作
shared_ptr还定义了一些其他的操作,参考前面的shared_ptr操作表格,例如,我们可以用reset将一个 新的指针赋予一个shared_ptr:
#include <iostream>
#include <memory>
using namespace std; int main()
{
shared_ptr<string> p1(new string("helloworld--1"));
// p1 = new string("helloworld2--2");//error!
p1.reset(new string("helloworld2--2"));
cout<<*p1<<endl;
}
与赋值类似,reset会更新(-1)引用计数,如果需要的话,会释放p1指向的对象。reset成员经常与unique一起使用,来控制多个shared_ptr的共享对象。在改变底层对象之前,我们在检查自己是否是当前对象仅有的用户。如果不是,在改变之前要做一份新的拷贝:
#include <iostream>
#include <memory>
using namespace std; int main()
{
shared_ptr<string> p1(new string("helloworld--1"));
shared_ptr<string> p2(p1); if(p1.unique())
cout<<*p1 + string(" is unique!")<<endl;
else{
p1.reset(new string("new reset!"));
cout<<*p1<<endl;
}
}
容器中的shared_ptr-记得用erease节省内存
对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,他就不会被释放掉。由于这个特性,保证shared_ptr在不用之后不再保留就非常重要了,通常这个过程能够自动执行而不需要人工干预,有一种例外就是我们将shared_ptr放在了容器中。所以永远不要忘记erease不用的shared_ptr。
#include <iostream>
using namespace std;
int main()
{ list<shared_ptr<string>>pstrList;
pstrList.push_back(make_shared<string>(""));
pstrList.push_back(make_shared<string>(""));
pstrList.push_back(make_shared<string>(""));
pstrList.push_back(make_shared<string>("")); for(auto p:pstrList)
{
if(*p == "");
{
/*do some thing!*/
}
cout<<*p<<endl;
} /*包含"3333"的数据我们已经使用完了!*/
list<shared_ptr<string>>::iterator itr = pstrList.begin(); for(;itr!=pstrList.end();++itr)
{
if(**itr == ""){
cout<<**itr<<endl;
pstrList.erase(itr);
}
} cout<<"-------------after remove------------"<<endl;
for(auto p:pstrList)
{
cout<<*p<<endl;
} while(1)
{
/*do somthing other works!*/
/*遍历 pstrList*/ //!这样不仅节约了大量内存,也为容器的使用增加了效率
}
}
状态共享——why use shared_ptr?
使用shared_ptr在一个常见的原因是允许多个多个对象共享相同的状态,而非多个对象独立的拷贝!
#include <iostream> using namespace std; void copyCase()
{
list<string> v1({"","b","d"});
list<string> v2 = v1; //!v1==v2占用两段内存 v1.push_back("cc"); //!v1!=v2 for(auto &p:v1){
cout<<p<<endl;
}
cout<<"--------void copyCase()---------"<<endl;
for(auto &p:v2){
cout<<p<<endl;
}
} //v1和v2分属两个不同的对象,一个改变不会影响的状态。 void shareCase()
{
shared_ptr<list<string>> v1 = make_shared<list<string>>(,"bb");
shared_ptr<list<string>> v2 = v1; (*v1).push_back("c2c");
for(auto &p:*v1){
cout<<p<<endl;
}
cout<<"----------shareCase()--------"<<endl;
for(auto &p:*v2){
cout<<p<<endl;
}
} //v1和v2属于一个对象的两个引用,有引用计数为证,其内容的改变是统一的。 int main()
{
copyCase();
cout<<"++++++++++++++++"<<endl;
shareCase();
}
智能指针与异常
异常发生后,常规的动态内存常常不能正确释放。但是如果使用智能指针,即程序过早结束,智能指针也能确保在内存不需要时将其释放:
void f()
{
shared_ptr<int>sp(new int(42)) ;
}
函数的推出,要么有两种情况,正常处理结束或者发生了异常,无论哪种情况,局部对象都会被销毁。在上面的程序中,sp是一个shared_ptr,因此sp销毁时会检查引用计数。在此例中,sp是指向这块内存的唯一指针。所以会被释放掉。
与之相对的,当发生异常时,我们直接管理的内存时不会自动释放的,如果使用内置指针管理内存,且在new之后对应的delet之前发生异常,则内存不会释放。
void f()
{
int *p = new int(42);
//code//!异常抛出,且没有在f()中被捕获。
delete p;
}
如果在new和delete之间发生异常,且异常未在f()中捕获,则内存就永远不会被释放了。
shared_ptr对象的销毁
1)管理动态数组
默认情况下,shared_ptr指向的动态的内存是使用delete来删除的。这和我们手动去调用delete然后调用对象内部的析构函数是一样的。与unique_ptr不同,shared_ptr不直接管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自定义的删除器来替代delete 。
#include <iostream>
using namespace std; class DelTest
{
public:
DelTest(){
j= ;
cout<<" DelTest()"<<":"<<i++<<endl;
}
~DelTest(){
i = ;
cout<<"~ DelTest()"<<":"<<i++<<endl;
}
static int i,j;
}; int DelTest::i = 0;
int DelTest::j = 0; void noDefine()
{
cout<<"no_define start running!"<<endl;
shared_ptr<DelTest> p(new DelTest[]); } void slefDefine()
{
cout<<"slefDefine start running!"<<endl;
shared_ptr<DelTest> p(new DelTest[],[](DelTest *p){delete[] p;});
} //!传入lambada表达式代替delete操作。 int main()
{
noDefine(); //!构造10次,析构1次。内存泄漏。
cout<<"----------------------"<<endl;
slefDefine(); //!构造次数==析构次数 无内存泄漏
}
通过自定义删除器的方式shared_ptr虽然管理的是一个动态数组。但是shard_ptr并不支持下标运算符的操作。而且智能指针类型不支持指针算术运算(不能取地址)。因此为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素。
2)管理非常规动态对象
某些情况下,有些动态内存也不是我们new出来的,如果要用shared_ptr管理这种动态内存,也要自定义删除器。
#include <iostream>
#include <stdio.h>
#include <memory>
using namespace std; void closePf(FILE * pf)
{
cout<<"----close pf after works!----"<<endl;
fclose(pf);
} int main()
{
// FILE * fp2 = fopen("bin2.txt", "w");
// if(!pf)
// return -1;
// char *buf = "abcdefg";
// fwrite(buf, 8, 1, fp2);
// fclose(fp2);
shared_ptr<FILE> pf(fopen("bin2.txt", "w"),closePf);
cout<<"*****start working****"<<endl;
if(!pf)
return -;
char *buf = "abcdefg";
fwrite(buf, , , pf.get()); //!确保fwrite不会删除指针的情况下,可以将shared_ptr内置指针取出来。
cout<<"----write int file!-----"<<endl;
} //!即可以避免异常发生后无法释放内存的问题,也避免了很多人忘记执行fclose的问题。
在这里可以设想一下TCP/IP中链接的打开和关闭的情况,同理都可以使用智能指针来管理。
总结:
最后总结一下上面所陈述的内容,也是shared_ptr使用的基本规范
1)不使用相同的内置指针值初始化(或reset)多个智能指针。
2)不delete get函数返回的指针。
3)如果你使用了get返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
4)如果你使用智能指针管理的资源不是new分配的内存,记得传递给他一个删除器。
weak_ptr
weakptr使用的比较少,如有兴趣了解,请去参考该篇文章:https://www.cnblogs.com/DswCnblog/p/5628314.html
C++内存管理之shared_ptr的更多相关文章
- C++动态内存管理之shared_ptr、unique_ptr
C++中的动态内存管理是通过new和delete两个操作符来完成的.new操作符,为对象分配内存并调用对象所属类的构造函数,返回一个指向该对象的指针.delete调用时,销毁对象,并释放对象所在的内存 ...
- (六)boost库之内存管理shared_ptr
(六)boost库之内存管理shared_ptr 1.shared_ptr的基本用法 boost::shared_ptr<int> sp(new int(10)); //一个指向整数的sh ...
- cocos2d-x c++和object-c内存管理比较
转自:http://www.2cto.com/kf/201307/227142.html 既然选择了C++作为游戏开发的语言, 手动的管理内存是难以避免的, 而Cocos2d-x的仿Objctive- ...
- [基础] C++与JAVA的内存管理
在内存管理上(总之一句话——以后C++工程,一定要用智能指针!) 1.同是new一个对象,C++一定得手动delete掉,而且得时刻记住能delete的最早时间(避免使用空指针).JAVA可以存活于作 ...
- GC与显式内存管理
C++复兴的话题至今已被鼓吹两年有余,Herb Sutter和Bjarne Stroustrup等大牛们也为C++带来了大步伐的革新.然而,从这两年的效果而言,C++的复兴并没有发生.一方面随着世界经 ...
- C++内存管理学习笔记(5)
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
- c++内存管理方式
概述 本章总结一些关于个人对内存管理的理解,主要包括如下内容: 内存管理原则 优秀的接口 智能指针的作用在哪里? 内存管理原则 学c++的同学都知道这个内存管理原则,就是“谁创建,谁释放”或者说“谁申 ...
- 2、COCOS2D-X内存管理机制
在C++中.动态内存分配是一把双刃剑,一方面,直接訪问内存地址提高了应用程序的性能,与使用内存的灵活性.还有一方面.因为程序没有正确地分配与释放造成的比如野指针,反复释放,内存泄漏等问题又严重影响着应 ...
- boost之内存管理
内存管理一直是令C++程序员最头疼的工作,C++继承了C那高效而又灵活的指针,使用起来稍微不小心就会导致内存泄露.野指针.越界访问等访问.虽然C++标准提供了只能指针std::auto_ptr,但是并 ...
随机推荐
- PHP中的use、命名空间的理解
看.Net中的命名空间和using using Ddd.Core; using Ddd.Core.Caching; using Ddd.Core.Data; using Ddd.Core.Domain ...
- sysfs文件系统
3 sysfs文件系统 sysfs是一个基于内存的文件系统,它的作用是将内核信息以文件的方式提供给用户程序使用.该文件系统的目录层次结构严格按照内核的数据结构组织.除了二进制文件外(只有特殊场合才使用 ...
- python's twenty ninthday for me 模块和包
模块 和 脚本的 区别: 如果一个py文件被导入了,就是一个模块. 如果这个py文件被直接执行,这个被直接执行的文件就是一个脚本. 模块:1,没有具体的调用过程.2,能对外提供功能. pyc文件: ...
- day2-心得
模块sys和os #!/usr/bin/env python # -*- coding: utf-8 -*- import sys sys.path #打印环境变量 print(sys.argv) # ...
- 1.3Broker
Celery需要一种解决消息的发送和接受的方式,我们把这种用来存储消息的的中间装置叫做message broker, 也可叫做消息中间人. 作为中间人,我们有几种方案可选择: 1.RabbitMQ R ...
- Storm集群详细部署
1.安装zookeeper 3.1下载zookeeper安装包, 建议下载3.4.5及以上的版本 http://www.apache.org/dyn/closer.cgi/zookeeper/ 3.2 ...
- clock函数返回负值~ (转)
使用clock() 函数来进行计时,时不时的返回一个很大的负数,怎么检查也检查不出错误,现在找出错误原因,给大家分享一下. 来源网页:http://kebe-jea.blogbus.com/logs/ ...
- Pagination分页
基本语法 下面展示Paginator的基本使用 >>> from django.core.paginator import Paginator >>> object ...
- Spring MVC的配置
一.添加依赖 <dependency> <groupId>org.springframework</groupId> <artifactId>sprin ...
- Basics
[Basics] 1.You can declare multiple constants or multiple variables on a single line, separated by c ...