在C++中写面向对象程序与用其他的完全面向对象语言来编写程序有很大不同,根本原因在于——C++只传递值,不传递对象。事实上,这种不同应该归类为“不完全面向对象的语言”和“完全的面向对象语言”的区别之处,C++只是作为前者中的一个代表。
本系列文章以一个简单的数据库类为例,由浅入深,逐渐体会这种“完全”和“不完全”之间的差异。
文章的目标人群是C++初学者。写作目的,一是阐述这种“完全”和“不完全”之间的差异,二是希望在分析这一案例的过程中,加深大家对于面向对象中“封装、继承、多态”的理解,帮助大家学习面向对象的思想。
博主同样也是编程的新人,如果有任何疏漏之处,请多多包涵,不吝赐教。
#3.0 在C++中实现引用计数和自动垃圾回收
//尝试使用类机制掩盖堆栈内存的差异
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
//一切对象的基类
class Object
{
protected:
size_t reference_count;
Object() :reference_count(0) {}
};
//一切句柄的基类
template<typename T>
class Handle
{
virtual T deepcopy(const T&) = 0;
};
//deepcopy函数
template<typename T>
T deepcopy(const T& handle)
{
return handle.deepcopy();//多态实现deepcopy函数
}
//_Record实类
class _Record :virtual protected Object
{
friend class Record;
protected:
unsigned int id;
unsigned int gpa;
string name;
_Record() {}
_Record(const _Record& rec)
{
id = rec.id;
gpa = rec.gpa;
name = rec.name;
//此处是深拷贝,实类的所有拷贝都是深拷贝
}
~_Record() {}
_Record& operator=(const _Record& rec)
{
id = rec.id;
gpa = rec.gpa;
name = rec.name;
//深拷贝
return *this;
}
friend ostream& operator<<(ostream& out, const _Record& rec);//模仿py的__repr__函数
};
ostream& operator<<(ostream& out, const _Record& rec)
{
out << rec.id << ' ' << rec.name << ' ' << rec.gpa << endl;
return out;
}
//Record句柄类
class Record :virtual protected Handle<Record>
{
template <typename T> friend T deepcopy(const T& handle);//通常我们还需要一个深拷贝函数
public:
Record() : p(new _Record)
{
/*解决方案就是:
把所有真对象都放在堆里,而把堆的内存管理交由表指针自动完成*/
++p->reference_count;
}
Record(const Record& rec) :p(rec.p)
{
++p->reference_count;//浅拷贝
}
~Record()
{
--p->reference_count;
if (!p->reference_count)
{
delete p;
}
}
Record& operator=(const Record& rec)
{
++rec.p->reference_count;
this->~Record();
p = rec.p;//浅拷贝
return *this;
}
//以上四个函数由于有隐式默认函数,必须重写
unsigned int& id()
{
return p->id;
}
unsigned int& gpa()
{
return p->gpa;
}
string& name()
{
return p->name;
}
/*由于句柄类和实类的分离,隔绝了实类数据成员和外界的联系
这是不可避免的,虽然它使数据操作变得复杂,但是巧妙的是,这种绕弯式的操作恰恰迎合了数据封装的方便
这也就是一些高级语言中所谓的“属性方法”。
通过属性方法的返回值类型,我们可以轻易地实现对数据成员访问权限的精细控制:只读、读写*/
//以上是属性方法,复刻自实类公开的数据成员
friend ostream& operator<<(ostream& out, const Record& rec);
//复刻自实类的友元函数
/*句柄类的成员完全复刻自实类,这也意味着如果实类有所删改,这里也要一起删改*/
private:
_Record* p;//数据成员只有一根指针
Record(_Record* ptr) :p(ptr) {}
Record deepcopy(const Record& rec)
{
return Record(new _Record(*rec.p));
//搭配构造函数,创建后直接返回,将句柄对象的复制降到1
}
};
ostream& operator<<(ostream& out, const Record& rec)
{
cout << *rec.p;
return out;
}
//_Database实类
class _Database : virtual protected Object
//继承也十分讲究,必须virtual(为了满足“一切新类都要继承Object的原则”) protected(为了满足Object封装的要求)
{
friend class Database;
protected: //所有公开成员必须是保护类型(满足封装要求)
_Database() {};
_Database(const _Database& cpy)
{
_datatab = cpy._datatab;
};
~_Database() {};
_Database& operator=(const _Database& cpy)
{
_datatab = cpy._datatab;
}
//4个默认成员函数必须重写到protected标签
Record& operator[](size_t index)
{
return _datatab[index];
}
void insert(Record rec)
//终于可以潇洒地按值转递而不用担心速度问题!
{
_datatab.push_back(rec);
}
private:
vector<Record> _datatab;//这里是Record而不是_Record,风格!风格!
};
//Database句柄类
class Database :virtual protected Handle<Database>
{
template <typename T> friend T deepcopy(const T& handle);//通常我们还需要一个深拷贝函数
public:
//固定成员
Database() :p(new _Database)
{
++p->reference_count;
}
Database(const Database& cpy) :p(cpy.p)
{
++p->reference_count;
}
~Database()
{
--p->reference_count;
if (!p->reference_count)
{
delete p;
}
}
Database& operator=(const Database& rec)
{
this->~Database();
p = rec.p;//浅拷贝
++p->reference_count;
return *this;
}
//遮罩成员
Record& operator[](size_t index)
{
return p->operator[](index);
}
void insert(Record rec)
{
return p->insert(rec);
}
private:
_Database* p;
Database(_Database* ptr) :p(ptr) {}
Database deepcopy(const Database& cpy)
{
return Database(new _Database(*cpy.p));
}
};
/*几乎是Ctrl+C,Ctrl+V而来的函数*/
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);
temp = Record();//给句柄换新的对象
}
return db;
}
int main()
{
Database db = import_file("./RawData.txt");
for (int i = 0; i < 10; ++i)
{
cout << db[i];
}
}
/*CONGRATULATIONS!
我们已经成功地在C++中实现了超脱于内存限制的面向对象!
然而,我们要考虑一个问题————这一切值得吗?
事实上,STL标准库可没有继承我们的Object类和Handle类!
也就是说,当我们写一个函数按值传递string、vector、map等等时,仍然会经历几次深度拷贝!
这就是我所说的“风格不一致”的问题,事实上,由于C++继承自C,STL都是继承C风格的
也就是说,如果我们硬要在C++中使用这种自动垃圾回收,那必须在基础八九种C类型之外,把STL类类型也区别对待。
一套语言、两套标准,这绝不是一件有利于开发的好事。*/
/*问题所在:
我们要反省一个问题:
WHAT MAKES C++?
臃肿了3倍的代码、双倍的维护开销、复杂的继承原则————这都是为了实现自动垃圾回收的代价
这3个问题,都是“写代码”层次的机械劳动,如果非要这样,为什么不改进编译器,干脆让它做这件事算了?
如果我们真的这么干了,那么实际上,一个新的语言就诞生了……
总而言之,如果我们真的这么干了,为什么不去用JAVA或者C#?
可见,当我们庆祝自动垃圾回收的潇洒时,我们已经把C++的优势给丢了————C风格*/
/*Linus Torvalds:C++一切优秀的地方,都在于它继承自C的地方
用C的方式写面向对象,或者说,在不完全面向对象的语言写面向对象
C的面向过程的风格,就是它们相对于完全面向对象语言的唯一优势,也是最大优势————速度*/
下一篇链接:https://blog.csdn.net/u014132143/article/details/90212694