C++以智能指针管理内存资源

版权声明:感谢您对博文的关注!校招与社招,有需要内推腾讯的可以QQ(1589276509)or 微信(louislvlv)联系我哈,期待您的加入。 https://blog.csdn.net/K346K346/article/details/83040070

1.简介

C++作为一门应用广泛的高级编程语言,却没有像Java、C#等语言拥有垃圾回收(Garbage Collection )机制来自动进行内存管理,这也是C++一直被诟病的一点。C++在发展的过程中,一直致力于解决内存泄漏,C++虽然基于效率的考虑,没有采用垃圾回收机制,但从C++98开始,推出了智能指针(Smart Pointer)来管理内存资源,以弥补C++在内存管理上的技术空白。

智能指针是C++程序员们一件管理内存的利器,使用智能指针管理内存资源,实际上就是将申请的内存资源交由智能指针来管理,是RAII技术的一种实现。RAII是C++的之父Bjarne Stroustrup教授提出的概念,RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中获取资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。

“资源获取即初始化”,在使用智能指针管理内存资源时,“资源”指的是通过new或malloc申请的内存资源,“初始化”指的是使用申请的内存资源来初始化栈上的智能指针类对象。使用智能指针管理内存资源的好处显而易见,通过智能指针对象在生命周期结束时,自动调用析构函数,在析构函数中完成对内存资源的释放,即自动调用内存资源的释放代码,避免因忘记对内存资源的释放导致内存泄漏。

2.实例

下面看一个使用由C++11引入的智能指针unique_ptr来管理内存资源的例子。

#include <memory>
#include <iostream>
using namespace std;

class A
{
public:
	A() {}
	~A() 
	{
		cout<<"A's destructor"<<endl;
	}
	void Hello() 
	{
		cout<<"use smart pointer to manage memory resources as far as possible"<<endl;
	}
};

int main()
{
	unique_ptr<A> pA(new A);
	pA->Hello();
	return 0;
}

程序输出:

use smart pointer to manage memory resources as far as possible
A's destructor

可见在main()函数结束后,类A的析构函数被自动调用,完成了内存资源的释放。

在创建智能指针对象时,也可以暂时不指定内存资源,先创建一个空的智能指针对象。空智能指针对象不可以进行任何操作,但可以使用get()成员函数来判断是否存在内存资源,如果为空则可以指定内存资源。类似于如下操作:

unique_ptr<int> pInt;
if (pInt.get()==nullptr)
{
	pInt.reset(new int(8));
	cout<<*pInt<<endl;
}

使用unique_ptr智能指针来管理内存资源时,是对内存资源的独占式管理,即内存资源的所有权不能进行共享,同一时刻只能有一个unique_ptr对象占有某个内存资源。如果发生赋值或拷贝构造,则会在编译期报错,因为unique_ptr禁止了拷贝语义,提高了代码的安全性。

unique_ptr<int> pInt(new int(8));
unique_ptr<int> pInt1=pInt;	//编译报错
unique_ptr<int> pInt2(pInt);	//编译报错

当然,可以通过移动语义完成内存资源的所有权转移,转移之后,原智能指针对象变为空智能指针对象,不能再对内存资源进行任何操作,否则会发生运行时错误,但我们也可以使用get()成员函数进行判空处理。

unique_ptr<int> pInt(new int(8));
unique_ptr<int> pInt1=std::move(pInt);		//转移所有权
*pInt=6;																//对空智能指针进行赋值操作将报运行时错误
if(!pInt.get())														//判空处理更安全
{
	*pInt=6;
}

独占式的内存资源管理可以使用unique_ptr来完成,但是如果想对内存资源进行共享式管理,那么unique_ptr就无能为力了。shared_prt使用引用计数来实现对内存资源的共享式管理,当对内存资源的引用计数变为0时,由最后一个对内存资源拥有管理权的智能指针对象完成对内存资源的释放。

#include <memory>
#include <iostream>
using namespace std;

class A
{
public:
	A() {}
	~A()
	{
		cout << "A's destructor" << endl;
	}
	void Hello()
	{
		cout << "use smart pointer to manage memory resources as far as possible" << endl;
	}
};

int main()
{
	shared_ptr<A> spInt(new A);		//接管内存资源
	cout << "reference count "<<spInt.use_count() << endl;
	shared_ptr<A> spInt1 = spInt;	//spInt1获取内存资源的管理权
	spInt1->Hello();
	cout << "reference count " << spInt.use_count() << endl;
	spInt1.reset();						//spInt1放弃对内存资源的管理权
	cout << "reference count " << spInt.use_count() << endl;
}

程序编译运行结果:

reference count 1
use smart pointer to manage memory resources as far as possible
reference count 2
reference count 1
A's destructor

3.智能指针使用注意事项

智能指针虽然增强了安全性,避免了潜在的内存泄漏,但是我们在使用时还是应该遵守一定的规则,以保证代码的健壮性。
(1)smart_ptr<T>不等于T*,使用时不能完全按照T*来使用。因为smart_ptr<T>本质上是类对象,一个用于管理内存资源的智能指针类对象,而T*是一个指向类型T的指针,二者不能随意地转换和赋值;
(2)使用独立的语句将newed对象置入智能指针,因为使用临时智能指针对象可能会引发内存泄漏。比如下面的语句:

process(shared_ptr<A>(new A),foo());

实际上对process()函数调用时编译器需要完成如下三步为process()准备好实参。
(2.1)调用函数foo();
(2.2)执行new A表达式;
(2.3)调用shared_ptr<A>构造函数,初始化智能指针对象。
实际上,不同的编译器在执行上述三个语句时可能会有不同的顺序,如果编译器将(2.2)放在(2.1)之前执行,执行顺序如下:
(2.1)执行new A表达式;
(2.2)调用函数foo();
(2.3)调用shared_ptr<A>构造函数,初始化智能指针对象。
如果在调用函数foo()时抛出异常,那么new A表达式产生的指向堆对象指针将会丢失,于是产生了内存泄漏。解决办法就是使用独立的语句将newed对象置入智能指针,做法如下:

shared_ptr<A> spA(new A);
process(spA,foo());

参考文献

[1]改善C++程序的150个建议[M].建议34:用智能指针管理通过new创建的对象
[2]Effective C++ 中文第三版[M].条款13:以对象管理资源
[3]Effective C++ 中文第三版[M].条款14:以独立语句将newed语句置入智能指针

猜你喜欢

转载自blog.csdn.net/K346K346/article/details/83040070