条款31:将文件间的编译依存关系降至最低

本文探讨了C++中通过分离接口与实现来减少编译依赖的方法。介绍了如何使用前置声明、智能指针及抽象类等手段,降低类之间的耦合度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们还是从一个例子开始出发:

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;

};

我们加上需要的头文件就可以通过编译了:

  #include <string>
  #include "date.h"

  #include "address.h"

但是不幸的是,这么一来便在Person定义文件和其含入文件之间形成了一种编译依存关系。如果这些头文件中有一个被改变或者头文件所依赖的文件被改变,那么每一个含有Person class的文件都得重新编译。这样的编译会对许多项目造成难以形容的灾难。

  一种解决办法是这么做:

namespace std{

  class 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;

};

    如果这么做Person客户端只需要在Person接口被修改过时才重新编译。但是这个想法存在两个问题。第一,string 不是个class,它是个typedef(basic_string<char>)。因为是标准库所以这个问题可以解决,#include就可以了。

    关于前置声明的第二问题是,编译器必须知道编译期间对象的大小。考虑这个:

    int main()

   {

      int x;

      Person p(params);

      ...

    }

    编译器看到x就知道分配一个int大。编译器看到p时也需要知道Person对象的大小,所以它需要询问class的定义式。此问题在JAVA语言中将被看成下面这样:

  int main()

  {

    int x;

    Person* p;

    ...

  }

  所以我们也可以玩玩同样的游戏:把Person分成两个class,一个提供接口,一个负责实现该接口(PersonImpl)。

 #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::string address() const;

    ...

    private:

     std::tr1::shared_ptr<PersonImpl> pImpl;

 }

    这里Person class只有一个成员指针,指向其实现类。这样就实现了接口与实现的完全分离,那些class的任何实现的修改都不需要Person客户端重新编译。这种分离的关键在于以“声明的依赖性”替换为“定义的依赖性”。这里有一些策略:

    1.如果使用对象的指针或者引用完成,就不要使用对象。

    2.如果能够,尽量以class的声明式替换class的定义式

    3.为声明式和定义式提供不同的头文件。为了严守规则需要两个头文件,一个用于声明式一个用于定义式。当然他们得保持一致性。因此程序库客户应该总是#include一个声明文件而非前置若干函数。例如:Date的客户希望声明today和clearAppointments,不应该像先前手工的前置声明Date,而是应该#include:

#include "datefwd.h"

Date today();

void clearAppointments(Date d);

像Person这样使用实现的class被称为Handle classes,下面是Person两个成员函数的实现:

#include "Person.h"

#include "PersonImpl.h"

Person::Person(const std::string& name,const Date& birthday,const Address& addr)

           :pImpl(new PersonImpl(name,birthday,addr))

     {   }

std::string Perosn::name() const

{

   return pImpl->name();

}

另外一种方法是Person变成一个抽象类:

class Person{

   public:

   virtual ~Person() = 0;

   virtual std::string name() const = 0;

   virtual std::string birthDate() const = 0;

   virtual std::string Address() const = 0;

  ...

};

Perosn class 的客户必须有办法为这种class创建新对象,他们通常这样实现:

static:

class Person{

  public:

  ...

  static std::tr1::shared_ptr<Person>

    create(const std::string& name,const Date& birthday,const Address& addr);

  ...

};

客户这样使用:

std::string name;

Date dateOfBirth;

Address address;

...

std::tr1::shared_ptr<Person>pp(Person::create(name,dateOfBirth,address));

...

std::cout<<pp->name().....;

假设:

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;

};

有了RealPerson之后就可以写出Person::create()了:

std::tr1::shared_ptr<Person>Person::create(const std::string& name,

                                     const Date& birthday,const Address& addr)

{

   return std::tr1::shared_ptr<Person>(new RealPerson(name,birthday,addr))

}

RealPerson 示范实现接口类最常用的机制之一:从接口类继承接口规格,然后实现出接口所覆盖的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值