当编译器看到 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; ...
private: // ptr to implementation; std::tr1::shared_ptr<PersonImpl> pImpl; }; // std::tr1::shared_ptr
不声明 Date 就可以声明 today 和 clearAppointments 的能力可能会令你感到惊奇,但是它其实并不像看上去那么不同寻常。如果有人调用这些函数,则 Date 的定义必须在调用之前被看到。为什么费心去声明没有人调用的函数,你想知道吗?很简单。并不是没有人调用它们,而是并非每个人都要调用它们。如果你有一个包含很多函数声明的库,每一个客户都要调用每一个函数是不太可能的。通过将提供类定义的责任从你的声明函数的头文件转移到客户的包含函数调用的文件,你就消除了客户对他们并不真的需要的类型的依赖。