天天看點

讀書筆記 ---- C++中的接口與實作分離

目錄

        • 前言
        • 問題
        • 方法一:"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:将檔案間的編譯依存關系降至最低

繼續閱讀