EffectiveC++读书笔记——item31(最小化编译依赖)

1. 编译依赖问题引入

在 C++ 中,类定义不仅包含接口,还包含大量实现细节,这导致文件间存在编译依赖。例如Person类:

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

定义Person类的文件需#include stringDateAddress相关头文件,若这些头文件或其依赖文件改变,包含Person类及使用Person的文件都需重新编译。

2. 前向声明的问题

尝试通过前向声明减少依赖存在两个问题:

  • 标准库类型声明问题stringtypedef,其前向声明复杂且不应手动声明,应直接使用#include
  • 对象大小确定问题:C++ 编译器需通过类定义确定对象大小,若类定义省略实现细节,编译器无法得知对象大小。例如:
int main() {
    int x;
    Person p(params); 
}

编译器可根据已知信息为int型变量x分配空间,但无法仅通过省略实现细节的Person类定义为p分配空间。

3. pimpl 惯用法(Handle 类)

  • 设计思路:将类拆分为仅提供接口的主类和实现接口的类,主类仅包含指向实现类的指针,如Person类可设计为:
#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的客户无需关注DateAddress等细节,实现改变时客户无需重新编译,且减少客户对实现细节的依赖。
  • 实现示例
#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 Person::name() const {
    return pImpl->name();
}

4. Interface 类

  • 设计思路:作为抽象基类为派生类指定接口,通常无数据成员、有虚析构函数和纯虚函数。例如:
class Person {
public:
    virtual ~Person();
    virtual std::string name() const = 0;
    virtual std::string birthDate() const = 0;
    virtual std::string address() const = 0;
   ...
};
  • 对象创建:客户通过factory函数(如create)创建对象,该函数返回指向动态分配对象的智能指针。例如:
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));
  • 派生类实现:如RealPersonPerson派生并实现其虚函数:
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;
};

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));
}

5. 两种方法的代价与使用建议

  • 代价
    • Handle 类:成员函数访问数据增加间接层,需额外存储指针,存在动态内存分配成本及异常风险。
    • Interface 类:函数调用为虚拟,存在间接跳转成本,对象可能需额外存储虚表指针。两者都不利于大量使用inline函数。
  • 建议:开发过程中使用Handle类和Interface类减少实现变化对客户的影响,产品阶段若速度和大小影响显著,可考虑用具体类替代。

6. 总结

最小化编译依赖的核心是用对声明的依赖替代对定义的依赖,Handle类和Interface类是基于此的有效方法。库头文件应保持完整且仅包含声明,无论是否涉及模板均适用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值