一、引言
本篇智能指针单指shared_ptr指针。
大概需要完成以下主要功能:
1、无参构造函数
2、有参构造函数(传入指针)
3、拷贝构造函数
4、赋值运算符重载
5、功能函数:返回引用计数…(不再列举)
6、析构函数
二、需求分析
1、构造函数中,加入explicit关键字,防止原始指针隐式的转化为智能指针。
2、当智能指针指向的内存为nullptr时,引用计数的值为0。
3、智能指针内部封装了一个原始指针成员,指向目标内存,类型不确定,故采用模板类书写该类。
4、由于不同智能指针指向同一块内存时,共享引用计数;故需要定义一个int* 指针成员,指向堆区的引用计数的值。
5、delete nullpt是时安全的,故下述代码有这样的写法,使代码更紧凑。
三、代码书写
类代码实现:
#include<iostream>
using namespace std;
template<class T>
class Shared_ptr {
public:
//无参构造
Shared_ptr():m_ptr(new T), m_count(new int(0)) {}
//有参构造
explicit Shared_ptr(T* p) :m_ptr(p), m_count(new int) {
if (!p) {
*(this->m_count) = 0;
}
else {
*(this->m_count) = 1;
}
}
//拷贝构造
Shared_ptr(const Shared_ptr& p) {
this->m_ptr = p.m_ptr;
this->m_count = p.m_count;
(*(this->m_count))++;
}
/*重点,面试常考*/
//赋值运算符重载(p1指this对象,p2指形参)
Shared_ptr& operator=(const Shared_ptr& p) {
if (this == &p) return *this;//p1和p2指向同一块内存 无操作
//步骤1:考虑是否清空p1
if ((*this->m_count) > 1) {
(*(this->m_count))--;
}
else{ //引用计数为1/0,清空p1的内存和引用计数
delete this->m_ptr;
delete this->m_count;
this->m_ptr = nullptr;
this->m_count = nullptr;
}
//步骤2:p2赋值给p1做的操作
if (p.m_ptr != nullptr) { //p2不为空
this->m_ptr = p.m_ptr;
this->m_count = p.m_count;
}
else { //p2为空
this->m_ptr = p.m_ptr;
this->m_count = new int(0);//重新开辟一块内存记录0,防止析构时重复释放
}
return *this;
}
//功能函数:获得引用计数
int getCount() {
return *m_count;
}
/*此处还可根据shared_ptr的一些功能,灵活添加函数*/
//析构函数
~Shared_ptr() {
if (!m_ptr || (*m_count == 1)) { // 只有当前指针指向空或者只有一个指向对象时释放内存
delete m_count;
delete m_ptr;
m_ptr = nullptr;
m_count = nullptr;
}
else { // 否则只减少引用计数
--(*m_count);
}
}
private:
T* m_ptr;//原始指针
int* m_count;//引用计数
};
//测试代码
void test1() {
// 检查构造函数是否正常工作
Shared_ptr<int> p1(new int(1));
cout << "p1 cnt:" << p1.getCount() << endl;
// 检查拷贝构造是否正常工作
{
Shared_ptr<int> p2(p1);
cout << "p2 cnt:" << p2.getCount() << endl;
cout << "p1 cnt:" << p1.getCount() << endl;
}
//cout << << endl;
// 检查引用计数是否正常工作
cout << "p1 cnt:" << p1.getCount() << endl;
// 检查默认初始化时计数是否正常
Shared_ptr<int> p4;
cout << "p4 cnt:" << p4.getCount() << endl;
// 检查拷贝赋值是否正常工作
p4 = p1;
cout << "p1 cnt:" << p1.getCount() << " p4 cnt:" << p4.getCount() << endl;
Shared_ptr<int> p_left(new int(100));
Shared_ptr<int> p_right(nullptr);
p_left = p_right;
cout << "p_left cnt:" << p_left.getCount() << endl;
}
int main() {
test1();
system("pause");
return 0;
}
四、需注意的点(重载赋值运算符)
此代码参考了https://blog.youkuaiyun.com/zsiming/article/details/125542007
但该博客未考虑如下情况(即将一个空的指针指针赋值给其他智能指针):
Shared_ptr<int> p_left(new int(100));
Shared_ptr<int> p_right(nullptr);
p_left = p_right;
在该博客中,由于在测试函数中加入了这几行代码,导致析构时程序崩溃。
原因是:p_right的引用计数m_count和p_left 指向了同一块内存(记为a),且值均为0;这样就导致p_left 和p_right进入析构函数时,由于检测到引用计数为0,会对内存a析构2次,造成程序崩溃。
解决方法:
1、将浅拷贝转为深拷贝,赋值时,p_left 重新开辟一段内存记录引用计数0值(原来的空间被释放),不和 p_right 指向同一块引用计数内存(不会影响到后续该指针的使用,因为指向新内存时,其引用计数的指向会跟着改变);
2、不将原来 p_left 的引用计数内存(值为0)释放覆盖。
我的代码采用的是第一种方法:
......
this->m_ptr = p.m_ptr;
this->m_count = new int(0);//重新开辟一块内存记录0,防止析构时重复释放
......
重新开辟了一块堆区,存放引用计数0。这样代码更为紧凑。
如有不足,敬请指正。