C++知识点34——动态内存与智能指针

一、动态内存

动态内存所在的位置在堆区,由程序员手动分配并手动释放,而不像栈内存由系统分配和自动释放

C++通过new运算符为对象在堆上分配内存空间并返回该对象的地址,并用delete运算符销毁对象并释放对象所占的动态内存,如果分配动态内存后没有手动释放会产生内存泄露

new一个对象时分为三步:1、调用operator new或operator new[]来分配内存空间。分配内存空间这一步,实质上调用的是malloc。2、编译器调用构造函数构造对象。3、构造完成后,返回一个指向对象的指针

delete时,分为两步:调用指针所指对象的析构函数销毁对象。2、调用operator delete或者operator delete[]释放内存空间,回收内存空间这一步,实质上也是调用的是free

示例

void newexample()
{
	int *pi=new int;
	string *ps=new string;
	cout<<*pi<<","<<*ps<<endl;

	int *pi1=new int();
	string *ps1=new string(10, 's');
	cout<<*pi1<<","<<*ps1<<endl;

	vector<int> *pv=new vector<int>{10, 20, 30};
	cout<<(*pv)[0]<<endl;
}

上面的代码说明了如何用new动态分配内存并创建对象,如果new后边的类型名后边没有括号,那么将创建一个默认初始化的对象,如果new后边的类型名有大括号,则创建了一个列表初始化的对象,如果new后边的类型名带有小括号,则是按照构造函数的形式进行直接初始化

但是上边的代码将产生内存泄漏,因为pi,ps,pi1,ps1,pv都是局部变量,当函数执行结束后,局部变量(这些指针)将被自动释放,所以,之后再也没有任何指针能指向并释放new创建的对象的内存,也就造成了内存泄露,解决办法就是在函数退出之前使用delete运算符释放指针所指向的对象内存

delete pi, pi1, ps, ps1, pv;

如果函数返回一个指向动态内存的指针,那么接收该指针的拷贝的对象要负责释放指向的内存

指针被delete后,指针变为空悬指针(指向内存中的数据已经无效),避免空悬指针的办法就是delete指针后将指针置为空,这样在使用前对指针判空就能知道指针不可以再解引用。但是这个办法也是有限的

示例

void danglingpointer()
{
	string *pa= new string("1234");
	string *pb=pa;
	delete pb;
	pb=nullptr;
	if (pa!=nullptr) {
		cout<<*pa<<endl;
	}
}

上述代码中pa和pb都指向同一块内存,将pb指针delete后,pa指向的数据也被释放,然后将pb指向空,但是pa此时仍然指向原先的内存,不是空指针,对pa解引用之后,程序出现段错误

new也可以动态分配const对象,因为const对象创建时必须初始化,所以new时,也必须初始化,因为是const对象,所以返回的是一个指向const对象的指针

const int *pc=new const int(1024); 

new除了可已创建单个对象,还可以创建对象数组,只需要在对象类型后面加方括号

示例

void newarray()
{
	int *p1=new int[10];
	int *p2=new int[10]{1,2,3,4,5};
	string *p3=new string[10];
	string *p4=new string[10]();
}

其中,第三行和第五行对10个元素进行默认初始化,第四行是列表初始化,第六行是值初始化

上面的代码也是存在内存泄露的,只不过在用delete释放资源时,语法不同

	delete []p1;
	delete []p2;
	delete []p3;
	delete []p4;

需要在delete和指针之间加上方括号

当用new创建数组时,方括号中的数字可以小于0,知道有这么个东西就行,实际写代码不要这样写

此外,无论是用new创建单个对象还是对象的数组,返回的都是对象类型的指针,C/C++中,指针并不知道指向的是单个对象还是对象的数组

 

二、智能指针

当使用局部变量的指针指向new创建的对象时,容易产生内存泄露,而应对措施有限,所以为了防止内存泄露,C++设计了三个智能指针:share_ptr, weak_ptr,  unique_ptr,使用这三个智能指针之前,要#include<memory>

2.1 shared_ptr

shared_ptr是C++标准库中的模板,允许多个shared_ptr对象同时指向一个对象,内部通过引用计数得到有多少个shared_ptr对象指向该对象,当最后一个shared_ptr对象被销毁时,被指向的对象会自动销毁,将所占的内存释放。

2.1.1 shared_ptr的常用构造函数

constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t) : shared_ptr() {}

template <class U> explicit shared_ptr (U* p);
template <class U, class D> shared_ptr (U* p, D del);
template <class D> shared_ptr (nullptr_t p, D del);

shared_ptr (const shared_ptr& x) noexcept;
template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;

template <class U> explicit shared_ptr (const weak_ptr<U>& x);
template <class U, class D> shared_ptr (unique_ptr<U,D>&& x);

示例

void shareptr()
{
	shared_ptr<int> spi;//默认构造
	shared_ptr<int> spn(nullptr);//空指针构造
	shared_ptr<int> spi1(new int(1));//第三个构造
	shared_ptr<int> spi2(new int[10], default_delete<int>());//第四个构造函数
	shared_ptr<int> spi3=spi1;//(spi1), 拷贝构造函数

	weak_ptr<int> wpi3=spi3;
	shared_ptr<int> spi4(wpi3);//通过weak_ptr初始化的构造函数
	shared_ptr<int> spi5(unique_ptr<int>(new int));//通过unique_ptr初始化的构造函数

	cout<<spi.use_count()<<","<<spn.use_count()<<","<<spi1.use_count()<<","
	<<spi2.use_count()<<","<<spi3.use_count()<<","<<spi4.use_count()<<","<<spi5.use_count()<<","
	<<wpi3.use_count()<<endl;
}

上面的代码中default_delete是一个函数对象类,返回一个函数对象作为删除器,删除器也可以自己定义,当智能指针指向一个普通对象时,通过default_delete<T>()得到的函数对象会调用delete,当智能指针指向一个对象的数组时,通过default_delete<T[]>()得到的函数对象会调用delete[]

use_count成员函数返回共享对象的智能指针的个数,其中spi1,spi3和spi4共享new int(1)

2.1.2 shared_ptr的常用操作

explicit operator bool() const noexcept;
element_type& operator*() const noexcept;
element_type* operator->() const noexcept;

这三个操作说明可以像普通指针一样使用智能指针判空和解引用

示例

void useshareptr()
{
	shared_ptr<vector<int>> vsp(new vector<int>(10,1));
	if (vsp) {
		cout<<vsp->back()<<endl;
		cout<<(*vsp).front()<<endl;
	}
}

除了上述和普通指针一样的操作常规外,还有一些其他操作如下

element_type* get() const noexcept;//返回智能指针中保存的指针

void reset() noexcept;//释放智能指针原来指向的对象
template <class U> void reset (U* p);//释放智能指针原来指向的对象并重新初始化智能指针
template <class U, class D> void reset (U* p, D del);//释放智能指针原来指向的对象并重新初始化智能指针,并令智能指针的删除器为del

void swap (shared_ptr& x) noexcept;//交换两个智能指针
bool unique() const noexcept;//等价于sp.use_count()==1

除了上述操作外,还有一个非成员函数make_shared,返回的是一个shared_ptr,通常用于初始化智能指针,使用make_shared时,make_shared的参数必须与模板参数类型的构造函数相匹配

template <class T, class... Args>
shared_ptr<T> make_shared (Args&&... args);

示例

void shareptrnewope()
{
	shared_ptr<int> spi=make_shared<int>(10);
	spi.reset();
	int *psp=spi.get();
	cout<<(psp==nullptr)<<endl;
	spi.reset(new int(20), default_delete<int>());
	cout<<*spi<<endl;
}

 

2.2 weak_ptr

weak_ptr也是一种智能指针,一般会和shared_ptr绑定在一起,但是,当用weak_ptr和shared_ptr绑定时,并不能增加shared_ptr的引用计数(也就是说,weak_ptr并不延长或缩短对象的生命周期),当一个对象的最后一个shared_ptr被销毁后,weak_ptr指向的对象也会被释放。可以说,weak_ptr是shared_ptr的附属品

2.2.1 weak_ptr的构造函数

constexpr weak_ptr() noexcept;//默认构造函数

weak_ptr (const weak_ptr& x) noexcept;//拷贝构造函数
template <class U> 
weak_ptr (const weak_ptr<U>& x) noexcept;
template <class U> 
weak_ptr (const shared_ptr<U>& x) noexcept;

构造函数没有删除器参数,是因为weak_ptr不能控制对象的生命周期

示例

void weakptr()
{
	weak_ptr<int> wp1;
	weak_ptr<int> wp2(wp1);
	shared_ptr<int> sp=make_shared<int>(10);
	wp2=sp;
	cout<<wp1.use_count()<<endl;
	cout<<wp2.use_count()<<endl;
}

上述代码也说明,weak_ptr只有绑定shared_ptr,才能增加自身的引用计数

2.2.2 weak_ptr的常用操作

void reset() noexcept;//将weak_ptr置为空
void swap (weak_ptr& x) noexcept;
long int use_count() const noexcept;
bool expired() const noexcept;//当use_count()==0,返回true,否则返回false
shared_ptr<element_type> lock() const noexcept;//如果expired()返回true,那么lock返回一个空的shared_ptr,
否则返回一个指向weak_ptr对象的shared_ptr

因为weak_ptr指向的对象的生命周期受shared_ptr影响,当使用weak_ptr时,其指向的对象可能已经不存在,所以,weak_ptr不支持解引用操作和箭头操作,只能用lock获取shared_ptr,从而操作对象

示例

void weakptrope()
{
	shared_ptr<int> sp=make_shared<int>(10);
	weak_ptr<int> wp(sp);
	wp.reset();
	cout<<sp.use_count()<<endl;
	cout<<wp.use_count()<<endl;
	cout<<*sp<<endl;
	cout<<wp.expired()<<endl;
	shared_ptr<int> sp2=wp.lock();
	cout<<sp2.use_count()<<endl;
}

上述代码中,wp和sp都指向同一个对象,虽然wp被重置,但是并不影响sp的生命周期,只是wp自身被置空而已

 

2.3 unique_ptr

unique_ptr也是一个能防止内存泄漏的智能指针,这点和shared_ptr相同,和shared_ptr不同的是,unique_ptr所指向的对象在只能被一个unique_ptr所占有,也就是说unique_ptr独享指向的对象,一旦一个unique_ptr被销毁或者指向其他对象,先前指向的对象也会被释放。此外,unique_ptr还可以指向数组,这点和shared_ptr不同,shared_ptr只能指向单个对象

2.3.1 unique_ptr的构造函数和赋值

template <class T, class D = default_delete<T>> class unique_ptr;
template <class T, class D> class unique_ptr<T[],D>;//与数组绑定的unique_ptr的声明

constexpr unique_ptr() noexcept;
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}
explicit unique_ptr (pointer p) noexcept;
unique_ptr (pointer p, typename conditional<is_reference<D>::value,D,const D&> del) noexcept;

unique_ptr (const unique_ptr&) = delete;//拷贝和赋值都是delete,拷贝元素和赋值运算被禁用,所以unique_ptr不支持拷贝和赋值运算
unique_ptr& operator= (const unique_ptr&) = delete;

虽然unique_ptr不支持拷贝和赋值操作,但是还是有办法将一个unique_ptr的所占有的资源转移给别的unique_ptr,那就是使用std::move

template <class T>
typename remove_reference<T>::type&& move (T&& arg) noexcept;

示例

void uniqueptrinit()
{
      unique_ptr<int> up1;
	unique_ptr<int> up2(nullptr);
	unique_ptr<int> up3(new int(100),default_delete<int>());
	unique_ptr<int> up4(move(up3));
	cout<<(up3==nullptr)<<endl;
	cout<<*up4<<endl;
}

可见,当使用move后,up3被销毁,up4接收了原来up3所指向的对象,因为是智能指针,所以,当函数结束后,智能指针被自动销毁,指向的对象也被销毁

2.3.2 unique_ptr的常用操作

pointer get() const noexcept;//返回unique_ptr保存的指针,意义同shared_ptr
pointer release() noexcept;//将指向对象的指针返回并将unique_ptr对象置为空
void reset (pointer p = pointer()) noexcept;//释放unique_ptr原来指向的对象并接受新对象的指针p
void swap (unique_ptr& x) noexcept;

和make_shared类似同样有make_unique,同样,make_unique的参数必须与模板参数类型的构造函数相匹配

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args );//创建指向单个对象的unique_ptr

template< class T >
unique_ptr<T> make_unique( std::size_t size );//创建指向数组对象的unique_ptr

如果一个unique_ptr指向单个对象,和shared_ptr一样,支持bool、解引用和箭头操作,如果unique_ptr指向一个数组,那么还支持下标操作

explicit operator bool() const noexcept;
typename add_lvalue_reference<element_type>::type operator*() const;
pointer operator->() const noexcept;
element_type& operator[](size_t i) const; 

示例

void uniqueope()
{
	unique_ptr<list<int>> ulp=
		make_unique<list<int>>(10,6);
	list<int> *lp=ulp.get();
	ulp->insert(lp->begin(), 1);
	cout<<*ulp->begin()<<endl;

	ulp.reset(new list<int>{1,2,3,4,5});
	list<int> *lp2=ulp.release();
	cout<<(ulp==nullptr)<<endl;

	unique_ptr<int[]> up=make_unique<int[]>(10);
	up[0]=10;
	ulp.reset();//等价于reset(nullptr)
	up.reset(nullptr);
}

 

参考

《C++ Primer》

《C++标准库》

http://www.cplusplus.com/reference/memory/shared_ptr/

http://www.cplusplus.com/reference/memory/weak_ptr/

http://www.cplusplus.com/reference/memory/unique_ptr/

https://zh.cppreference.com/w/cpp/memory/unique_ptr/make_unique

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值