智能指针
C++中的智能指针分为4类,分别是:
- 共享指针(std:shared_ptr)
- 独占指针(std::unique_ptr)
- auto_ptr
- weak_ptr
其中,auto_ptr已被C++11标准摒弃,C++17标准已经不可用。
智能指针的出现,能够很好的解决原始指针因为忘记释放内存而导致的一系列问题,或是因为删除不彻底而形成的空悬指针问题。
接下来,我将分别介绍不同的智能指针及其具体的使用方法
shared_ptr
shared_ptr的声明
shared_ptr声明在头文件memory中,且存在于命名空间std中
其基本模板
template <class T> class shared_ptr;
这里并不是很好理解,可以换个方式看
std::shared_ptr<typename T> ptr_name;
// 这里首先要声明命名空间std
// typename,即类型名称,不只是内置类型,也可以是用户自定义类型,如类类型
// ptr_name,即对象名称
下面演示如何声明一个shared_ptr类型的智能指针
#include <memory> //引入memory头文件
int main(void){
std::shared_ptr<int> p;
}
shared_ptr的初始化
shared_ptr有多种重载的初始化方式,下面介绍几种常用的方法
- 直接初始化(调用了重载的构造函数):
shared_ptr的初始化只能由一个指针类型来完成,且一般是一个new开辟的动态内存的指针类型,直接初始化也正是调用了这种构造函数完成的初始化
template<typename T>
std::shared_ptr<T> p(new T()); //这里执行了值初始化
//如果类型T存在默认的构造函数,则会执行默认构造函数
- make_shared函数初始化:
相较于第一种方法,我更推荐第二种采用make_shared函数进行初始化的方法,原因如下:
使用make_shared创建的智能指针,可以免去new带来的额外内存花销,更加高效和安全
而且new开辟动态内存时,可能会发生开辟失败并抛出一个bad_alloc异常
这些都无疑提高了程序的性能
shared_ptr的使用
shared_ptr进行内存释放遵循一下原则:
- 当一个shared_ptr指针的引用计数为0时,进行对象的内存释放
- 当shared_ptr的生命周期结束时,进行内存释放,而无论该指针的引用计数是否为0
下面我将介绍一下有关shared_ptr的引用计数,这是智能指针实现智能释放内存的关键所在,每当对一个shared_ptr进行拷贝或引用操作时,该指针的引用计数递增1,这些指针共享同一块内存,所以该指针称为共享指针,每当有一个指针进行释放操作后,引用计数递减1,当引用计数为0时,即使生命周期还未结束,该指针指向的对象一样会进行内存释放。
#include<iostream>
#include<memory>
class Func { //创建一个类,用以说明智能指针的释放时机和生命周期的关系
public:
Func();
~Func(); //声明构造和析构函数
};
Func::Func() {
std::cout << "Func is built." << std::endl; //打印提示构造函数已被调用
}
Func::~Func() {
std::cout << "Func is destroyed." << std::endl; //打印提示析构函数已被调用
}
int main(void) {
std::shared_ptr<Func> p1 = std::make_shared<Func>(); //创建一个shared_ptr类型指针
std::shared_ptr<Func> p2(p1);
std::shared_ptr<Func> p3(p1);
std::shared_ptr<Func> p4(p1);
std::shared_ptr<Func> p5(p1); //拷贝构造
//use_count()用来统计一个对象被多少shared_ptr指针共享
std::cout << p1.use_count() << std::endl; //结果为5
p1.reset();
p2.reset();
p3.reset();
p4.reset();
p5.reset(); //空参数列表的reset函数用以释放shared_ptr指针的内存
//当所有共享指针全部释放完成后,执行类的析构函数,尽管这些指针的生命周期还没有结束
std::cout << "This is a signal." << std::endl; //在析构函数提示打印后才打印出来
return 0;
}
//该程序结果如下:
//Func is built.
//5
//Func is destroyed.
/This is a signal.
uinque_ptr
unique_ptr的声明
与shared_ptr类似,unique_ptr的声明如下
#include <memory>
template<typename T>
std::unique_ptr<T> ptr_name;
unique_ptr的初始化
在C++14标准以前,并没有提供一个类似于make_shared的函数返回一个shared_ptr智能指针,C++14引入了make_unique函数,其功能与make_shared类似,都是返回一个相关类型的智能指针
#include <iostream>
#include <memory> //引入头文件
std::unique_ptr<int> p = std::make_unique<int>(10); //初始化方式与make_shared类似
std::cout<< *p <<std::endl;
//打印结果为10
同样,unique_ptr也支持使用new进行直接初始化
#include <memory>
#include <iostream>
#include <new>
std::unique_ptr<int> p(new int(10))
std::cout<< *p <<std::endl;
//打印结果为10
unique_ptr的使用
与shared_ptr不同,一个unique_ptr指针无法支持普通的拷贝或赋值操作,因为它是一个“独享”类型的指针
同shared_ptr一样,unique_ptr也支持通过reset函数来重置该对象;可以通过get函数来返回一个内置类型的指针,该指针是一个指向所管理对象的原始裸指针。
注意,在使用get函数返回的指针时,当智能指针被销毁时,该指针仍然存在,有可能导致空悬指针的出现,所以应当谨慎使用get函数返回的指针。
此外,unique_ptr还支持release函数,通过这个函数,可以改变unique_ptr的指向,举例如下:
#include <iostream>
#include <memory>
#include <new>
int main(void) {
std::unique_ptr<int> p = std::make_unique<int>(10);
std::unique_ptr<int> q;
std::cout << "p = " << *p << std::endl;
//std::cout << "q = " << *q << std::endl; // 无法执行,这是一个未定义行为
auto n_p = p.release(); // n_p存储了p指针的原始数据,release之后,该智能指针被销毁
p = nullptr; // 注意,这里一定要将p指针置空,否则会出现空悬指针,引发内存泄露风险
//p.reset();
q = std::make_unique<int>(*n_p); // 将数据赋予新指针q
//std::cout << "p = " << *p << std::endl; // release之后,同样无法调用,也是一个未定义行为
std::cout << "q = " << *q << std::endl;
return 0;
}
// 结果为:
//10
//10
// 进行了一个数据传递
weak_ptr(弱引用智能指针)
weak_ptr的声明
该指针声明同前面一样,不再赘述
#include<memory>
std::weak_ptr<int> w_p;
weak_ptr的初始化
该指针的初始化必须与shared_ptr指针相关联
std::shared_ptr<int> p = std::make_shared<int>(20);
std::weak_ptr<int> w_p(p); // 声明了一个shared_ptr,并以它初始化了一个weak_ptr指针
weak_ptr的使用
weak_ptr不同于上述两种智能指针,它本身并没有重载*或->运算符,所以它是无法直接访问其指向的对象的,但我们可以通过函数lock来访问weak_ptr指向的shared_ptr对象,如果shared_ptr存在,则返回一个shared_ptr类型,否则返回一个false
#include<iostream>
#include<memory>
#include<new>
int main(void) {
std::shared_ptr<int> p = std::make_shared<int>(10);
std::weak_ptr<int> w_p = p; // 使用一个shared_ptr初始化weak_ptr指针
std::cout << "The shared_ptr value is " << *p << std::endl;
// 引用计数为1
std::cout << "The number of reference is " << p.use_count() << std::endl;
// std::cout << *w_p << std::endl;
if (w_p.lock()) { // 检查shared_ptr指针的存在性
std::cout << "The weak_ptr value is " << *w_p.lock() << std::endl;
// 返回一个shared_ptr指针,所以可以进行解引用操作
}
return 0;
}