C++面向对象与堆栈内存管理(二)

在C++中写面向对象程序与用其他的完全面向对象语言来编写程序有很大不同,根本原因在于——C++只传递值,不传递对象。事实上,这种不同应该归类为“不完全面向对象的语言”和“完全的面向对象语言”的区别之处,C++只是作为前者中的一个代表。

本系列文章以一个简单的数据库类为例,由浅入深,逐渐体会这种“完全”和“不完全”之间的差异。

文章的目标人群是C++初学者。写作目的,一是阐述这种“完全”和“不完全”之间的差异,二是希望在分析这一案例的过程中,加深大家对于面向对象中“封装、继承、多态”的理解,帮助大家学习面向对象的思想。

博主同样也是编程的新人,如果有任何疏漏之处,请多多包涵,不吝赐教。

#2.0 承接上文的改进版

//考虑到引用传参不需要进行复制,能否利用引用传参提高程序速度呢?

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

using namespace std;

class Record
{
	friend ostream& operator<<(ostream& out, const Record& rec);
public:
	unsigned int id;
	unsigned int gpa;
	string name;
};
ostream& operator<<(ostream& out, const Record& rec)
{
	out << rec.id << ' ' << rec.name << ' ' << rec.gpa << endl;
	return out;
}

class Database
{
public:
	Record& operator[](size_t index)
	{
		return _datatab[index];
	}
	void insert(Record& rec)//传引用
	{
		_datatab.push_back(rec);
	}

private:
	vector<Record> _datatab;
};

/*
Database& import_file(string file_path)
{
	ifstream fin(file_path);
	Database db;
	Record temp;
	while (fin >> temp.id >> temp.name >> temp.gpa)
	{
		db.insert(temp);
	}
	return db;//错!不能返回栈内的对象!因为在返回值到达调用处之前,对象已经析构了!
}
*/

Database& import_file(string file_path)
{
	ifstream fin(file_path);
	Database& db = *new Database;//只能这样写,才能突破栈层次的限制
	Record temp;
	while (fin >> temp.id >> temp.name >> temp.gpa)
	{
		db.insert(temp);//虽然程序运行正确,但是该处有很大的逻辑错误!
	}
	return db;
}

int main()
{
	//Database db = import_file("./RawData.txt");//恭喜你,内存泄漏
	Database& db = import_file("./RawData.txt");//这样才能不内存泄漏
	for (int i = 0; i < 10; ++i)
	{
		cout << db[i];
	}
	delete& db;//同时还不能忘记释放db占用的堆空间!
	/*这里,很坑的地方就出现了:
	你必须知道哪些函数返回的是堆空间对象的引用,这就是说,你写到任何时候都要对之前的函数知根知底
	要知道你可能写很多返回引用的函数,有的是堆对象,有的是不能释放的栈对象和全局对象(如18行的<<重载),堆对象必须delete,栈对象绝不能delete
	而且有些时候一个对象可能跨好几层栈被使用,你必须保证在最后一次使用前没有一次delete,而最后一次用完又必须delete
	假设你已经写了一万行代码,你还能像现在这样那么清醒吗?!68、69行和栈对象十分相似,在一万行代码里,要怎么区分!*/
}

/*总结:
通过引用传参,确实解决了对象传递时的无意义拷贝问题
但是它也带来了新的问题:因为这种传参对堆栈内存的敏感性,使得开发者需要对返回参数的堆栈位置非常清楚
也就是说,程序员必须清楚一个附加于返回值之外的信息,在函数很多,参数传递错综复杂的时候,维护这段代码是极其困难的挑战!
如果这里的代码出问题,那么debug是非常恶心的!由于释放全靠指针,这也是为什么很多书上都说指针“坏”的一面的由来*/

/*问题所在:
问题的根源就是由于程序运行时,内存的不是均质的,栈内存和堆内存差异巨大,这种差异也传承到了建立在内存上的对象上
很自然的,要解决这个问题,就会想到两种方法:
1.为什么还要分堆和栈?全用栈或者全用堆不就ok了吗?(不可行)
2.能否利用类机制巧妙地掩盖堆栈的差异呢?(自动垃圾回收)*/

 #2.1 内存泄漏的证明代码

//证明2中68行的内存泄漏

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

using namespace std;

class Record
{
	friend ostream& operator<<(ostream& out, const Record& rec);
public:
	unsigned int id;
	unsigned int gpa;
	string name;
};
ostream& operator<<(ostream& out, const Record& rec)
{
	out << rec.id << ' ' << rec.name << ' ' << rec.gpa << endl;
	return out;
}

class Database
{
public:
	Record& operator[](size_t index)
	{
		return _datatab[index];
	}
	void insert(Record& rec)
	{
		_datatab.push_back(rec);
	}

private:
	vector<Record> _datatab;
};

Database* p;

Database& import_file(string file_path)
{
	ifstream fin(file_path);
	Database& db = *new Database;
	Record temp;
	while (fin >> temp.id >> temp.name >> temp.gpa)
	{
		db.insert(temp);
	}
	p = &db;
	return db;
}

int main()
{
	Database db = import_file("./RawData.txt");//证明这里的内存泄漏
	db[0] = db[1];
	cout << db[0];
	cout << (*p)[0];
	delete& db;//这里也会报错
}

#2.2 逻辑错误的证明代码

//证明2中61行的逻辑错误

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

using namespace std;

class Record
{
	friend ostream& operator<<(ostream& out, const Record& rec);
public:
	Record() :name(new string)
	{
		//如果记录中包含一根指向堆数据的指针
	}
	~Record()
	{
		//很显然,堆内存要做到和对象同来同去,要不然类就毫无自动性和封装性可言了
		delete name;
	}
	unsigned int id;
	unsigned int gpa;
	string* name;
};
ostream& operator<<(ostream& out, const Record& rec)
{
	out << rec.id << ' ' << rec.name << ' ' << rec.gpa << endl;
	return out;
}

class Database
{
public:
	Record& operator[](size_t index)
	{
		return _datatab[index];
	}
	void insert(Record& rec)
	{
		_datatab.push_back(rec);
	}

private:
	vector<Record> _datatab;
};


Database& import_file(string file_path)
{
	ifstream fin(file_path);
	Database& db = *new Database;
	Record temp;
	while (fin >> temp.id >> *temp.name >> temp.gpa)
	{
		db.insert(temp);//到第二个他就错了
	}
	return db;
}

int main()
{
	Database& db = import_file("./RawData.txt");
	for (int i = 0; i < 10; ++i)
	{
		cout << db[i];
	}
	delete& db;
}

下一篇链接:https://blog.csdn.net/u014132143/article/details/90212683

发布了9 篇原创文章 · 获赞 6 · 访问量 1121

猜你喜欢

转载自blog.csdn.net/u014132143/article/details/90212547