目錄
-
-
-
- 前言
- 問題
- 方法一:"pimpl idiom"
- 方法二:Interface Classes
- 總結
- 參考文獻
-
-
前言
最近在讀《Effective C++》,對裡面的思想和代碼深有感觸,是以在此做點記錄并加以自己的了解,友善以後檢視。
本文内容來自
條款31:将檔案間的編譯依存關系降至最低
。
問題
通常我們會在一個類的定義中同時寫出聲明式和定義式,如
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:
//下面都是類成員變量的定義,是以這裡是定義式
std::string theName;
Date theBirthDate;
Address theAddress;
};
而使用這樣的寫法,很可能會導緻檔案包含的問題,比如Date類,Address類的頭檔案包含,同時當改變了Person類所依賴的某個類時,會導緻Person類重新編譯。這樣寫也沒有做到class的接口與實作分離。是以有兩種方法可以實作所謂的接口與實作分離,讓邏輯更清晰。
方法一:“pimpl idiom”
pimpl是"pointer to implementation"的縮寫,意為用指針指向其實作類來代替主類中的定義式部分。
代碼如下:
using Date = std::string;//僅供示例,是以不實作Date類了
using Address = std::string;
class PersonImpl {
public:
PersonImpl(const std::string &name, const Date& birthday, const Address& addr) {
this->theName = name;
this->theBirthDate = birthday;
this->theAddress = addr;
}
std::string name() const { return theName; }
std::string birthDate() const { return theBirthDate; }
std::string address() const { return theAddress; }
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
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:
std::shared_ptr<PersonImpl> pImpl;//Person的實作部分,使用指針,将Person類變成隻有接口的類,
//而把實作部分放到另一個類中實作,并引用這個類對象的指針來進行操作
};
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();
}
std::string Person::birthDate() const {
return pImpl->birthDate();
}
std::string Person::address() const {
return pImpl->address();
}
void showInfo(Person &p) {
std::cout << "---Person Info---" << std::endl;
std::cout << "Name: " << p.name() << std::endl;
std::cout << "Birthday: " << p.birthDate() << std::endl;
std::cout << "Address: " << p.address() << std::endl;
std::cout << "---The End-------" << std::endl;
}
int main(){
Person p("Jack", "10/09/2010", "NewYork");
showInfo<Person>(p);
return 0;
}
方法二:Interface Classes
将Person類定義為内含純虛函數的類,純虛函數即是Person類提供的接口。
代碼如下:
using Date = std::string;//僅供示例,是以不實作Date類了
using Address = std::string;
class Person {
public:
virtual ~Person() {}
static std::shared_ptr<Person> create(const std::string &name, const Date& birthday, const Address& addr);
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
};
class RealPerson : public Person {
public:
RealPerson(const std::string &name, const Date& birthday, const Address& addr)
: theName(name), theBirthDate(birthday), theAddress(addr) {
}
std::string name() const { return theName; }
std::string birthDate() const { return theBirthDate; }
std::string address() const { return theAddress; }
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
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));
}
int main()
{
std::shared_ptr<Person> pp = Person::create("Jack", "10/09/2010", "NewYork");
}
總結
來自《Effective C++》的三個設計政策:
- 如果可以使用對象引用或指針完成任務,就不要用對象 :因為隻靠一個類型聲明就可以定義出指向該類型的指針和引用,但如果要定義該類型的對象,則需要用到該類型的定義式。
- 盡量以class聲明式代替class定義式:因為當聲明一個函數,而它用到某個class時,它并不需要改class的定義,即使是使用pass by value的方式來傳參或者傳回值。
- 為聲明式和定義式提供不同的頭檔案:這樣做提供了程式設計時聲明該類的便利,也将聲明和實作分離,友善管理。
參考文獻
《Effective C++》 - 條款31:将檔案間的編譯依存關系降至最低