隐藏数据抽象的细节
● 在程序员创建类的对象之前,c++ 编译器要知道该类所有成员(私有、保护、公有)的细节, 因为编译器必须为对象分配足够的内存。 对象通常由客户创建,编译器在编译客户的程序时要知道类的大小,而客户只使用类的接口文件。
因此, 编译器必须能仅通过类的头文件即可确定任何类对象的大小。这就是在类的头文件中必须显示类的私有成员和保护成员的唯一原因。
在头文件中声明类的所有数据成员还有一个缺点,与编译程序的开销有关。 如果改变了类的大小, 该类的所有客户都必须用新的类头文件重新编译它们的程序。 在类中添加或移除数据成员时,类的大小会发生改变。
现在的问题是我们写完一个类后,在交付这个类后, 我们可能决定修改类的实现, 让它高效。 我们已经知道, 修改类的实现不会对类的接口有任何影响,因此,修改实现并不会影响客户。
假如现在我们在原来的类中添加了一些新的成员, 现在,每个使用这个类的客户必须重新编译他们的代码。 然而,强迫客户重新编译代码可不是好策略。 在改变类的实现时, 应该尽量不要强迫客户重新编译它们的代码。
数据抽象的好处之一,便是可以自由地修改实现。 如果仅仅因为修改实现可能导致对象大小的改变, 就放弃修改, 那我们就失去了数据抽象的主要优势之一, 需要想办法解决这个问题。
办法就是使用单独的实现类。 与其在头文件中保留类的所有数据成员, 不如只保留一个指向类对象实现的指针。 这个指针也称为句柄。 实现的细节置于其他类中。 通常,实现类的名称有后辍 Impl(或者甚至 Implementation).
原来的实现的类
#include<iostream>
using namespace std;
class TString
{
public:
TString();
TString(const char *s);
TString(char aChar);
private:
unsigned _strCapacity; //是指向缓冲区所能容纳的最大字符数
unsigned _length; // 是储存在对象中的字符数目
char *str;//指向字符的指针, _str 指向的内存至少要有 _length+1 长度,
};
下面,将使用单独的实现类,修改 TString 类
#include<iostream>
using namespace std;
class TStringImpl; //前置声明- 这是一个实现类
class TString
{
public:
TString();
TString(const char *s);
TString(char aChar);
private:
TStringImpl *_handle;
};
class TStringImpl
{
public:
unsigned _strCapacity; //是指向缓冲区所能容纳的最大字符数
unsigned _length; // 是储存在对象中的字符数目
char *_str;//指向字符的指针, _str 指向的内存至少要有 _length+1 长度,
};
// TString类的构造函数将创建TStringImpl类的对象, 并让 _handel指向它
TString::TString(const char *arg)
{
_handle = new TStringImpl; //创建一个新的实现类对象
if (arg && *arg) // 指针不是0, 它指向非零字符
{
_handle->_length = strlen(arg);
_handle->_str = new char[_handle->_length + 1];
_handle->_strCapacity = _handle->_