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