C++如何用智能指针管理内存资源

所属分类: 软件编程 / C 语言 阅读数: 265
收藏 0 赞 0 分享

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()准备好实参。

(1)调用函数foo();
(2)执行new A表达式;
(3)调用shared_ptr<A>构造函数,初始化智能指针对象。

实际上,不同的编译器在执行上述三个语句时可能会有不同的顺序,如果编译器将(2.2)放在(2.1)之前执行,执行顺序如下:

(1)执行new A表达式;
(2)调用函数foo();
(3)调用shared_ptr<A>构造函数,初始化智能指针对象。

如果在调用函数foo()时抛出异常,那么new A表达式产生的指向堆对象指针将会丢失,于是产生了内存泄漏。解决办法就是使用独立的语句将newed对象置入智能指针,做法如下:

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

以上就是C++如何用智能指针管理内存资源的详细内容,更多关于c++ 智能指针管理内存的资料请关注脚本之家其它相关文章!

更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多