手写智能指针的C++简单实现(赋值运算符重载时考虑空指针的情况)

本文介绍了如何实现一个简单的C++智能指针shared_ptr,包括无参和有参构造、拷贝构造、赋值运算符重载,以及析构函数。特别关注了赋值运算符的处理,防止对空指针的错误操作导致的双释放问题,通过重新分配内存来避免。此外,文章还讨论了防止原始指针隐式转换和引用计数的管理。
摘要由CSDN通过智能技术生成

一、引言
  本篇智能指针单指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.csdn.net/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。这样代码更为紧凑。
  如有不足,敬请指正。

实现一个手写输入法,你需要了解以下几点: 1. 手写识别算法:手写识别算法可以使用深度学习模型,例如卷积神经网络(CNN)或循环神经网络(RNN),也可以使用传统的机器学习算法,例如支持向量机(SVM)或随机森林(Random Forest)。这个部分需要有一定的数学知识和机器学习经验。 2. 输入法框架:你需要使用一个输入法框架,例如Windows的IME或Android的输入法框架。这个部分需要了解操作系统的输入法架构和API,以及对应的框架的使用方法。 3. 用户界面:你需要实现一个用户界面,让用户可以输入手写的汉字。你可以使用C++的GUI库,例如Qt或MFC,来实现这个部分。 下面是一个简单的示例,演示如何在Windows平台使用C++和MFC实现一个简单手写输入法。假设你已经有了一个手写识别模型,可以将手写输入转换为汉字字符串。 1. 首先,你需要创建一个MFC应用程序项目。在Visual Studio中,选择“新建项目”,然后选择“MFC应用程序”。 2. 在应用程序向导中,选择“单文档”应用程序类型,并勾选“使用MFC的ActiveX控件”选项。 3. 在“选项”对话框中,选择“ActiveX控件”选项卡,然后点击“添加”按钮。选择“Microsoft InkEdit Control 1.5”,然后点击“确定”按钮。 4. 在资源视图中,双击“IDD_HANDWRITING_DIALOG”对话框,打开对话框编辑器。 5. 在对话框编辑器中,拖拽“InkEdit Control”控件到对话框中。调整控件的大小和位置,使其适合你的需要。 6. 在类向导中,添加一个成员变量,类型为CInkEdit。这个变量将用于与InkEdit控件交互。 7. 重载对话框的OnInitDialog函数,在函数中获取InkEdit控件的指针,并设置一些控件属性。示例代码如下: ``` BOOL CHandwritingDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 获取InkEdit控件的指针 m_pInkEdit = (CInkEdit*)GetDlgItem(IDC_INKEDIT); // 设置控件属性 m_pInkEdit->EnableInkInput(); m_pInkEdit->SetAutoComplete(FALSE); m_pInkEdit->SetDefaultStrokes(); m_pInkEdit->SetDefaultColors(); return TRUE; // return TRUE unless you set the focus to a control } ``` 8. 在类中添加一个函数,用于将手写输入转换为汉字字符串。示例代码如下: ``` CString CHandwritingDlg::RecognizeHandwriting() { // 假设你已经有了一个手写识别模型 // 将手写输入转换为汉字字符串 CString strResult = Recognize(m_pInkEdit->GetInkData()); return strResult; } ``` 9. 在类中添加一个响应函数,用于处理“识别”按钮的点击事件。在函数中调用RecognizeHandwriting函数,并将结果显示在对话框中。示例代码如下: ``` void CHandwritingDlg::OnRecognize() { CString strResult = RecognizeHandwriting(); SetDlgItemText(IDC_RESULT, strResult); } ``` 10. 编译和运行程序,测试手写输入和识别的功能。 这只是一个简单的示例,实际实现中还需要处理更多的细节和异常情况。同,你也需要根据操作系统和输入法框架的不同,做出相应的修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值