问题描述
// file : Person.hpp#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 birthData() const; std::string address() const; ... private: std::string theName; Date theBirthDate; Address theAddress; }
当 Person.hpp 修改时,会导致依赖该文件的源码都要重新编译,导致漫长的编译时间。
问题表现为,头文件包含和依赖
如上所示, Person.hpp 中包含文件 date.h 和 address.h 即 Person.hpp 依赖于这两个文件,并递归依赖于这两个文件依赖的文件,当这两个文件及其依赖的文件有任何修改时,包含 Person.hpp 的源码文件都要重新编译。
解决方式 1a:前向声明
// file : Person.hpp #include <string>class Date;class Address; // 引入前置声明, 去掉头文件的包含 "date.h" 和 "address.h" (不正确)class Person{ public: Person( const std::string& name, const Date& birthday, const Address& addr ); std::string name() const; std::string birthData() const; std::string address() const; ... private: std::string theName; Date theBirthDate; Address theAddress; }// 上述声明在声明 Person 对象时,会导致问题,如下所示int main(){ int x; Person p( params ); // 创建对象时,编译器必须能够指导 Person 类型占用的内存大小, // 仅前向声明无法提供足够的 Date 和 Address 的类型信息 // 与此类比的是,python 和 java 等语言,Person 中的如 theBirthDate 变量都是指针。 // 在使用时需要经过一次指针的引用,增加部分内存,损失些许性能,极大降低了文件的耦合}
解决方式 1b:前向声明配合 pimpl (pointer to implemention)
// PersonFwd.hpp // 声明式和定义式两个文件,怪怪的, 据说是为了模板struct PersonImpl;class Date;class Address; // 引入前置声明, 去掉头文件的包含 "date.h" 和 "address.h"http:// file : Person.hpp #include "PersonFwd.hpp"#include <string>#include <memory.h>class Person{ public: Person( const std::string& name, const Date& birthday, const Address& addr ); std::string name() const; std::string birthData() const; std::string address() const; ... private: std::shared_ptr<PersonImpl> pImpl; // 指针指向实现物}// file : PersonImpl.hpp#include "date.h"#include "address.h"struct PersonImpl{ std::string theName; Date theBirthDate; Address theAddress; PersonImpl( const std::string& name, const Date& birthday, const Address& addr ): theName( name ), theBirthDate( birthday), theAddress( addr ) { } }
优点
Person.hpp 实现了与之前依赖的头文件 date.h 和 address.h 及由此递归依赖的文件的解耦,可是改为 PerImpl.h依赖上述文件,只是该依赖不会继续向上传递;
Person 类型的声明 Person.hpp 及实现 PersonImpl.h 的解耦, 修改 Person 类实现时,只需要修改 PersonImpl.h 即可,不会影响使用 Person 类的文件。
代价
成员函数必须通过 implemention pointer 取得对象数据,访问是间接的,且增加了 implemetion pointer 的指针的内存,增加了动态内存分配的管理开销,及 bad_alloc 异常的处理难度。
解决方式 2:abstract base class
// file : Person.hpp #include <string>#include <memory.h>class Person{ public: virtual ~Person(); virtual std::string name() const = 0; virtual std::string birthData() const = 0; virtual std::string address() const = 0; // 可以使用工厂方法创建对象,static member function static std::shared_ptr<Person> create( const std::string& name, const Date& birthday, const Address& addr ) { // 返回 ptr -> derived class obj。 // 有多个 derived class 时,入参中加入 derived class 的id, 字符串或者枚举,内部用 switch 返回对应类型对象 } // 也可以使用 virtual 构造函数 virtual Person() { } ... }// 客户必须以 Person 的 pointers 和 reference 编写程序,并指向 Person 的 derived class 对象。 // file : PersonImpl.hpp#include "date.h"#include "address.h"#include "Person.hpp"class PersonImpl : public Person {private: std::string theName; Date theBirthDate; Address theAddress;public: PersonImpl( const std::string& name, const Date& birthday, const Address& addr ): theName( name ), theBirthDate( birthday), theAddress( addr ) { } std::string name() const { ... }; std::string birthData() const { ... }; std::string address() const { ... }; }
其他解决方案
P143,编译依赖最小化的本质,尽量让头文件自我满足,万一做不到就尽量依赖其他文件的声明式,而非定义式;
P143,如果使用 object references 或 pointer 可以完成任务,就不要使用 object value, references 和 pointer 可以借助前向声明完成编译,object value 必须包含头文件。
P143,尽量以 class 声明式代替 class 定义式,特别是在声明函数时,可以使用前向声明完成编译,不需要一定包含头文件, by value 传参除外,必须要包含头文件。
作者:呆呆的张先生
链接:https://www.jianshu.com/p/5e0bd47876a1
共同學習,寫下你的評論
評論加載中...
作者其他優質文章