条款31:将文件间的编译依存关系将至最低

it2025-03-14  21

考虑如下代码:

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)); }
最新回复(0)