编译依赖
例子
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,string的定义。需要头文件
#include <string>
#include "date.h"
#include "address.h"
但是这样就构建了依存关系。编译依存关系。 如果有的头文件改变
与之关联的文件都得重新编译。对于大型项目是十分可怕的。
想法一,前置声明
namespace std{
class string; //前置声明(不对,见缺点1)
}
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 theName;
Date theBirthDate; // 即便是前置声明,这里编译器还是需要知道Date的大小。
Address theAddress;// 即便是前置声明,这里编译器还是需要知道Address的大小。所以不成功,依赖还是存在。
};
缺点
- string是一个typedef,正确的前置声明很复杂。因为涉及到templates。但是这不重要。因为你根本就不应该使用前置生命来声明标准库的对象。因为使用include来完成任务。
想法二,使用指针指向实现
#include <string>
#include <memory>
class PersonImpl;
class Date;
class Address;
class Person{
public:
// 函数本身只需要声明式即可。
Person(const std::string& name, const Date& birthday, const Address& addr); //这里用的全是reference所以是不需要定义式的。只需要声明式。
std::string name() const;
std::string bithDate() const;
std::string address() const;
private:
std::tr1::shared_ptr<PersonImpl> pImpl;
};
如此以来,使用Person的客户代码就不会依赖于PersonImpl的实现细节和Address以及Date的实现细节。这就实现了依赖解绑。
关于依赖
-
如果可以使用object reference和object
pointer可以实现目的。就不要使用objects.(原因,你只需要声明就可以定义出指向该类型的reference和pointer但是,如果定义objects就需要用到定义式。
-
如果可以,尽量使用class声明式取代class定义式。
class Date; //声明式 Date today(); //不需要定义式,只需要声明式 void clearAppointments(Date d) //不需要定义式,只需要声明式
当声明函数的时候,只需要对象的声明式即可,即便参数和返回值,是按照by
value的方式传递的。 -
为声明式,和定义式提供不同的头文件。比如
#include "datefwd.h" Date today(); //不需要定义式,只需要声明式 void clearAppointments(Date d) //不需要定义式,只需要声明式
如何实现的
#include "PersonImpl.h" //必须在实现文件中引入这个定义头文件
Person::Person(const std::string& name, cosnt Date& birthday, const Address& addr)
:pImpl(new PersonImpl(name, birthday, addr))
{}
std::string Person::name() const
{
return pImpl->name();
}
想法三,使用抽象类
//声明式,在这里即可。new也只需要声明式。
class Person{
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
static std::tr1::shared_ptr<Person> create(const std::string& name, const Date& birthday, const Address& addr)
{
return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday, addr));
}
};
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;
std::string birthDate() const;
std::string address() const;
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
Notes
- export
允许将template的定义式和声明式分开。放在不同文件中,但是不是所有的编译器都实现了这个东西。
使用指针来降低文件依赖和使用虚基类来降低文件依赖的缺点
- 丧失若干速度。你要付出一定的内存。因为需要额外的指针间接访问实际的调用函数。
- 但是去请记住2-8原则,80%的虚函数指针可能都没有被使用到。
- 你应该在程序实现的过程中动态调整是否使用handle classes和interface
classes.速度如果有影响,则修改关键的20%代码直接使用定义式。 - inline可以帮助handle classes和interface classes实现它们应有的目的。