考虑如下代码:
class Person { public: Person(const std::string& name, const Date& birthday, const Address& addr); std::string name() const; std::string brithDate() const; std::string address() const; //... private: std::string theName; // 实现细目 Date theBirthDate; // 实现细目 Address theAddress; // 实现细目 //... };如果不提供如下头文件定义:则是无法通过编译的
#include <string> #include "date.h" #include "address.h"但是这么一来,便是再Person定义文件和其含入文件之间形成了一种编译依存关系,如果这些头文件所依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译;
你可能有如下解决办法:
namespace std { class string; // 前置声明,不正确 } class Date; // 前置声明 class Address; // 前置声明 class Person { public: Person(const std::string& name, const Date& birthday, const Address& addr); std::string name() const; std::string brithDate() const; std::string address() const; //... private: std::string theName; // 实现细目 Date theBirthDate; // 实现细目 Address theAddress; // 实现细目 //... };针对string声明不正确的原因是因为string不是一个class;
针对Person我们可以这样做:把Person分割为两个classes,一个只提供接口,另一个负责实现该接口;如果负责实现的那个所谓class 取名为PersonImpl,Person定义如下:
#include <string> #include <memory> class PersonImpl; // Person实现类的前置声明 class Date; // 前置声明 class Address; // 前置声明 class Person { public: Person(const std::string& name, const Date& birthday, const Address& addr); std::string name() const; std::string brithDate() const; std::string address() const; //... private: std::shared_ptr<PersonImpl> pImpl; };再这样的设计之下,Person的客户就完全与Dates,Addresses,以及Persons的实现细目分离了
现实中让头文件尽可能自我满足,万一做不到,则让他与其他文件内的声明式(而非定义式)相依,其他每一件事都源于一下简单设计:
如果使用object references或者object pointers可以完成任务,就不要使用objects如果能够,尽量以class 声明式替换class定义式:注意,当你声明一个函数而它用到某个class时,你并不需要该class的定义,即使该函数以by value方式传递该类型的参数,如下: class Date; //class 声明式 Date today(); void clearAppointments(Date d); //Date 定义式 为声明式和定义式提供不同的头文件,比如Date的客户如果希望声明today和clearAppointments,他们不该像先前那样以手工方式前置声明Date,而是应该#include适当的,内含声明式的头文件: #include "datefwd.h" //这个头文件声明(但未定义) class Date Date toDay(); void clearAppointments(Date d);以下为Person两个成员函数的实现
#include "Person.h" #include "PersonImpl.h" Person::Person(const std::string& name, const Date& birthday, const Address& addr) : pImpl(new PersonImpl(name, birthday, addr)) { } std::string Person::name() const { return pImpl->name(); }另一个制作Handle class办法是,令Person成为抽象基类:
class Person{ public: virtual ~Person(); virtual std::string name() const = 0; virtual std::string birthDate() const = 0; virtual std::string address() const = 0; //... };这个class的客户必须以Person的指针或者引用来编写程序(因为不可以实例化一个抽象基类对象);
Interface class的客户必须有办法为这种class创建新对象.他们通常调用一个特殊函数,此函数扮演"真正将被具现化"的那个derived class的构造函数角色,这样的函数又往往再Interface class内被声明为static:
class Person { public: //... static std::shared_ptr<Person> create(const std::string &name, const Date &birthday, const Address &addr) };而客户会这样使用它们:
std::string name; Date dateOfBirth; Address address; //... // 创建一个对象,支持Person接口 std::shared_ptr<Person> pp(Person::create(name, dateOfBith, address); //... std::cout << pp->name() << pp->bithDate(); << pp->address(); //... 当pp离开作用域,对象会被自动删除支持Interface class接口的那个具体象类必须被定义出来,而且真正的构造函数必须被调用:
class RealPerson : public Person { public: RealPerson(const std::string &name, const Date &birthday, const Address &addr) : theName(name), theBirthDate(birthday), theAddress(addr) {} virtual ~RealPerson() {} std::string name() const; std::string brithDate() const; std::string address() const; private: std::string theName; // 实现细目 Date theBirthDate; // 实现细目 Address theAddress; // 实现细目 };有了RealPerson之后,就可以写出Person::create:
std::shared_ptr<Person> Person::create(const std::string &name, const Date &birthday, const Address &addr) { return std::shared_ptr<Person>(new RealPerson(name, birthday, addr)); }