private: std::string theName; // implementation detail Date theBirthDate; // implementation detail Address theAddress; // implementation detail }; 在这里,假如不访问 Person 的实现使用到的类,也就是 string,Date 和 Address 的定义,类 Person 就无法编译。这样的定义一般通过 #include 指令提供,所以在定义 Person 类的文件中,你很可能会找到类似这样的东西:
#include <string> #include "date.h" #include "address.h" 不幸的是,这样就建立了定义 Person 的文件和这些头文件之间的编译依靠关系。假如这些头文件中的一些发生了变化,或者这些头文件所依靠的文件发生了变化,包含 Person 类的文件和使用了 Person 的文件一样必须重新编译,这样的层叠编译依靠关系为项目带来数不清的麻烦。
你也许想知道 C++ 为什么坚持要将一个类的实现细节放在类定义中。例如,你为什么不能这样定义 Person,单独指定这个类的实现细节呢?
namespace std { class string; // forward declaration (an incorrect } // one - see below)
class Date; // forward declaration class Address; // forward declaration
Person p( params ); // define a Person ... } 当编译器看到 x 的定义,它们知道它们必须为保存一个 int 分配足够的空间(一般是在栈上)。这没什么问题,每一个编译器都知道一个 int 有多大。当编译器看到 p 的定义,它们知道它们必须为一个 Person 分配足够的空间,但是它们怎么推测出一个 Person 对象有多大呢?它们得到这个信息的唯一方法是参考这个类的定义,但是假如一个省略了实现细节的类定义是合法的,编译器怎么知道要分配多大的空间呢? 这个问题在诸如 Smalltalk 和 java 这样的语言中就不会发生,因为,在这些语言中,当一个类被定义,编译器仅仅为一个指向一个对象的指针分配足够的空间。也就是说,它们处理上面的代码就像这些代码是这样写的:
int main() { int x; // define an int
Person *p; // define a pointer to a Person ... } 当然,这是合法的 C++,所以你也可以自己来玩这种“将类的实现隐藏在一个指针后面”的游戏。对 Person 做这件事的一种方法就是将它分开到两个类中,一个仅仅提供一个接口,另一个实现这个接口。假如那个实现类名为 PersonImpl,Person 就可以如此定义:
#include <string> // standard library components // shouldn’t be forward-declared
#include <memory> // for tr1::shared_ptr; see below
class PersonImpl; // forward decl of Person impl. class class Date; // forward decls of classes used in
class Address; // Person interface class Person { public: Person(const std::string& name, const Date& birthday,const Address& addr); std::string name() const; std::string birthDate() const; std::string address() const; ...
class Date; // class declaration Date today(); // fine - no definition void clearAppointments(Date d); // of Date is needed 当然,传值通常不是一个好主意,但是假如你发现你自己因为某种原因而使用它,依然不能为引入不必要的编译依靠辩解。
不声明 Date 就可以声明 today 和 clearAppointments 的能力可能会令你感到惊异,但是它其实并不像看上去那么不同平常。假如有人调用这些函数,则 Date 的定义必须在调用之前被看到。为什么费心去声明没有人调用的函数,你想知道吗?很简单。并不是没有人调用它们,而是并非每个人都要调用它们。假如你有一个包含很多函数声明的库,每一个客户都要调用每一个函数是不太可能的。通过将提供类定义的责任从你的声明函数的头文件转移到客户的包含函数调用的文件,你就消除了客户对他们并不真的需要的类型的依靠。
#include "datefwd.h" // header file declaring (but not // defining) class Date
Date today(); // as before void clearAppointments(Date d); 仅有声明的头文件的名字 "datefwd.h" 基于来自标准 C++ 库的头文件 <iosfwd>。<iosfwd> 包含 iostream 组件的声明,而它们相应的定义在几个不同的头文件中,包括 <sstream>,<streambuf>,<fstream> 和 <iostream>。
C++ 还提供了 eXPort 要害字答应将模板声明从模板定义中分离出来。不幸的是,支持 export 的编译器非常少,而与 export 打交道的实际经验就更少了。结果是,现在就说 export 在高效 C++ 编程中扮演什么角色还为时尚早。
像 Person 这样的使用 pimpl 惯用法的类经常被称为 Handle 类。为了避免你对这样的类实际上做什么事的好奇心,一种方法是将所有对他们的函数调用都转送给相应的实现类,而使用实现类来做真正的工作。例如,这就是两个 Person 的成员函数可以被如何实现的例子:
#include "Person.h" // we’re implementing the Person class, // so we must #include its class definition
#include "PersonImpl.h" // we must also #include PersonImpl’s class // definition, otherwise we couldn’t call // its member functions; note that // PersonImpl has exactly the same // member functions as Person - their // interfaces are identical
static std::tr1::shared_ptr<Person> // return a tr1::shared_ptr to a new create(const std::string& name, // Person initialized with the const Date& birthday, // given params; see Item 18 for const Address& addr); // why a tr1::shared_ptr is returned ... }; 客户就像这样使用它们:
std::string name; Date dateOfBirth; Address address; ...
// create an object supporting the Person interface std::tr1::shared_ptr<Person> pp(Person::create(name, dateOfBirth, address));
...
std::cout << pp->name() // use the object via the << " was born on " // Person interface << pp->birthDate() << " and now lives at " << pp->address(); ... // the object is automatically // deleted when pp goes out of 当然,在某些地点,必须定义支持 Interface 类的接口的具体类并调用真正的构造函数。这所有的一切发生的场合,在那个文件中所包含虚拟构造函数的实现之后的地方。例如,Interface 类 Person 可以有一个提供了它继续到的虚函数的实现的具体的派生类 RealPerson:
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; // implementations of these std::string birthDate() const; // functions are not shown, but std::string address() const; // they are easy to imagine