解决泛型编程里的析构函数delete操作异常卡死

1. 问题说明

        今天在写泛型代码的时候遇到了C/C++最恐怖的事情——非法内存访问,编译器提示没有报错信息,但是在一运行程序就会崩溃或卡死,上网找了很多博客和资料也没有类似的情况,反复排查也找不到可以修改的地方,最后通过调用堆栈信息和查看断点局部变量,最终将错误原因锁定在了析构函数的delete操作符里,如下图所示:

然后经过对析构函数的一顿拷打,终于发现了异常原因——竟然是由于实体类对象和模板类对象之间进行了浅拷贝,导致程序运行结束时分配给这些对象(们)的空间被硬生生析构两次,直接被编译器判定非法内存操作给紧急拦停了,我真的吐。。。

2. 异常代码

模板类文件Vector.h代码:

#pragma once
#include <iostream>

using namespace std;

template <typename T>
class Vector {
public:
	// 构造函数
	Vector(int size = 128) {
		if (size > 0) {
			this->len = size;
			this->base = new T[this->len];
		}
	} 
	// 析构函数
	~Vector() {
		if (base != NULL) {
			delete[] base;
			base = NULL;
			len = 0;
		}
	}

	// 重载[]
	T& operator[](int index) {
		return *(base + index);
	}

	// 打印数据
	void printVector() const {
		for(int i = 0; i < len; i++) {
			cout << *(base + i) << " ";
		}
	}

private:
	T* base;
	int len;
};

主函数文件main.cpp代码: 

#include <iostream>
#include "Vector.h"

using namespace std;

class Student {
public:
	Student() {
		this->age = 0;
		this->name = "";
		this->dept = NULL;
	}
	Student(int age, string name, const char dept[]) {
		int length = sizeof(dept) + 1;
		this->dept = new char[length];
		this->age = age;
		this->name = name;
		strcpy_s(this->dept, length, dept);
	}
	~Student() {
		if (dept != NULL) {
			delete[] dept;
			dept = NULL;
			cout << "Student::~Student" << endl;
		}
	}

	// 重载<<符号
	friend ostream& operator<<(ostream& os,
		const Student& student);

private:
	int age;
	string name;
	char* dept;
};

ostream& operator<<(ostream& os, const Student& student) {
	os << student.age << student.name << ",专业:"
		<< student.dept << " ";
	return os;
}

int main(void) {

	Student s1(18, "张鹏", "软件工程");
	Student s2(19, "周飞", "土木工程");

	Vector<Student> stuVector(2);

	stuVector[0] = s1;
	stuVector[1] = s2;

	stuVector.printVector();

	return 0;
}

执行程序后就可以看到异常中断的地方在main.cpp文件里的析构函数~Student()中,当然析构函数这样写本身并没有错误,语法和逻辑都说得通,关键罪魁祸首是stuVector[0] = s1 这一步用的是浅拷贝,析构函数没有想象的那么智能,所以它并不知道这件事,然受直接就对着两个同样的空间连续delete,不得不说C/C++这bug藏的是真够深,如果不能及时发现错误源,估计很多人要和析构函数死磕到底,浪费大量的时间。 

3. 解决方法

        解决方案有两种,第一种方法就是简单粗暴,对析构函数做个横向升级,不让析构函数对delete过的空间再delete一次,也就是直接在析构函数~Student()里添加个判断机制,代码如下:

~Student() {
		if (dept != NULL) {
			if (*dept != NULL) {
				delete[] dept;
				*dept = NULL;
			}
			dept = NULL;
		}
	}

第二种方法就是重载operator=,不让Student对象之间相互进行浅拷贝,重载operator=也有种叫法是赋值构造函数,两者是一个概念,重载operator=代码如下:

// 重载=符号
	Student operator=(const Student& student) {
		this->age = student.age;
		this->name = student.name;
		this->dept = new char[sizeof(student.dept) + 1];
		strcpy_s(this->dept, sizeof(student.dept) + 1, student.dept);

		return Student(this->age, this->name, this->dept);
	}

记得重载函数写在Student类里。

4. 测试总结

        运行上述第二种方式修改后的代码如下图所示:

可以明显看到程序只创建了两个对象,但最后却被析构了四次,当然如果有读者采用第一种方法,只会析构两次,这里就不做单一的展示了。 有时候不得不感慨,C/C++藏bug是真的深。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Akgry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值