在学习了一段时间的C++后,开始读《Effective C++》。由于缺乏项目经验,平时写的程序大都也是在LeetCode和牛客上的C with STL,对编译过程也不太了解,所以读这一章时感觉有很多不理解的地方。在查阅了网上其他大佬对这一章的理解后,我想谈谈自己对这一条款的理解。
#include <string>
#include "date.h"
#include "address.h"
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 m_Name;
Date m_BirthDate;
Address m_Address;
};
首先是开头的这一段程序,在Person类中,有三个成员变量,它们的类型是string、Date和Address。由于Person类的三个成员变量既不是指针也不是引用,所以编译器在编译这个文件时必须访问这三个类型的定义式,否则无法得知该给这三个变量分配多少内存。也就是说,如果你修改了Date或Address的定义,然后重新编译,Person类所在的文件也不得不重新编译。
现在,假如Person类的三个成员变量不是Object,而是指针或引用,那么你可以不用include date.h和address.h,而用两个声明代替它们(class Date;,class Address;)。此时,你再修改Date和Address的定义,然后重新编译,Person类所在的文件就不用重新编译。
#include <string>
class Date;
class Address;
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* m_Name;
Date* m_BirthDate;
Address* m_Address;
};
为什么这样就不用重新编译Person所在的文件呢?
首先,对于指针类型,它的大小是固定的,在64位的系统上是8字节,32位系统上是4字节,编译器不需要访问定义式就可以知道所需要的内存大小。由此可见,对于编译器来说,Person类的每一个成员变量类型和分配给成员变量的内存是明确的。
其次,编译器在编译的时候,是将每个文件单独编译,最后再将所有文件连接。因此,就算你修改了Date和Address的定义式,也不会影响到Person,因为编译person的时候不需要访问Date和Address。
如果是引用原理也是一样,因为引用的本质还是指针。
基于此,作者提出了两条建议:尽量用指针和引用代替Object;尽量用class声明代替class定义式。
为什么要用指针和引用代替object,上面已经说明。而为什么用class声明代替class定义,是因为在函数声明中,即使用到某个class,并不需要访问定义式,即使是使用by value的方式传参。(我个人看来,使用class定义式并不方便修改,因为一旦修改了定义式,整个文件都需要重新编译,哪怕你只是做了一个小小的修改)
#include <string>
#include <memory>
class PersonImpl;
class Date;
class Address;
class Person {
public:
Person( const std::string& name,const Date& birthday,const Address& addr);
std::string name() const;
std::string birthDate() const;
std::address() const;
...
private:
std::tr1::shared_ptr<PersonImpl> pImpl;
};
上述代码就应用了作者的两个建议。用class声明代替了class定义式,用指针代替了Object。使用这种方式的好处是降低了耦合。因为将类型的实现细节都放在了 PersonImpl中,如果你修改了PersonImpl的定义式,可以不用重新编译Person。
想象一下,假如你有数百个文件使用了Person类型的对象,如果你将所有细节都放在Person中,一旦你修改了Person类,这就意味着这数百个文件都要重新编译。万幸的是,你将Peson类的细节都放在了PersonImpl中,而Person类只使用了指针,就算你修改了PersonImpl,也只需要重新编译PersonImpl所在的文件就可以了。
像上文中的Person类被称为Handle class,而定义Handle class的方法还有另一种。具体细节就不表了,书本上都有,再写下去就变成抄书了。
如有错误请指出,万分感谢。
|