• Effective C++笔记_条款31将文件间的编译依存关系降至最低


          这个章节,读了两遍还是不是很清楚,有一种没法和作者沟通的感觉,看来我还是一个C++的初学者呀。好吧,不多说了,回归主题,今天的笔记。

          章节一开始就弄了个例子来说明文件间的编译依存关系以及串联的依存关系。代码如下:

     1  #include<string>
     2  #include"date.h"
     3  #include"address.h"
     4  class Person {
     5   public:
     6       Person(const std::string& name, const Date& birthday, const Address& addr);
     7       std::string name() const;
     8       std::string birthDate() const;
     9       std::string address() const;
    10       //...
    11   private:
    12       /*实现条目*/
    13       std::string theName;
    14       Date theBirthDate;
    15       Address theAddress;
    16   };

    1.Person 定义文件和其含入文件之间形成:编译依存关系,这些头文件有任何一个被改变, 或这些头文件所依赖的其他头文件有任何改变,那么每一个含有Person class的文件就得重新编译,任何使用Person class的文件也必须编译:连串编译依存关系。

    2. 为了解决上述问题,可以把Person分割为两个classes,一个只提供接口(Person),另一个负责实现该接口(PersonImpl)。具体示例代码如下:

     1 // Person 接口
     2   #include <string>
     3   #include <memory>
     4 
     5   // 前置声明
     6   class PersonImpl;   // Handle classes
     7   class Date;
     8   class Address;
     9 
    10   class Person {
    11   public:
    12       Person(const std::string& name, const Date& birthday, const Address& addr);
    13       std::string name() const;
    14       std::string birthDate() const;
    15       std::string address() const;
    16       //...
    17   private:
    18       // pimpl idiom(pointer to implementation)
    19       std::tr1::shared_ptr<PersonImpl> pImpl;    // 指针,指向实现物,shared_ptr 在memory头文件
    20   };

    3.编译依存性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。
    (1)如果使用object reference或object pointers可以完成任务,就不要使用objects。
    (2)如果能够,尽量以class声明式替换class定义式。
    (3)为声明式和定义式提供不同的头文件。

     1  // Person 的Handle class,实现Person class中的接口
     2   #include "Person.h"   // Person的定义式
     3   // include PersonImpl的class定义式,用于调用其成员函数,
     4    // 注意PersonImpl有着和Person完全相同的成员函数。两者接口完全相同
     5   #include "PersonImpl.h"  
     6 
     7    Person::Person(const std::string& name, const Date& birthday, const Address& addr)
     8        :pImpl(new PersonImpl(name, birthday, addr))
     9        {}
    10 
    11    std::string Person::name() const
    12    {
    13        return  pImpl->name();            //让Person变成一个Handle class 并不会改变它做的事,智慧改变它做事的方法
    14    }

    4.方案二:Interface class ,令Person成为一种特殊的abstract base class
       Interface class:
      (1)   目的:详细一一描述derived classes的接口
       (2)通常不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual 函数,用来叙述整个接口。

    1 class Person {
    2   public:
    3       virtual ~Person();
    4       virtual std::string name() const = 0;
    5       virtual std::string birthDate() const = 0;
    6       virtual std::string address() const = 0;
    7       //...
    8   };

          Interface class 的客户,通常调用一个特殊函数(factory函数或virtual构造函数):扮演“真正将被具现化”的那个derived classes的构造函数角色。它们返回指针(智能指针),指向动态分配所得对象,而该对象支持Interface class的接口。它们通常被声明为static。

    1 class Person {
    2     //...
    3     static std::tr1::shared_ptr<Person>
    4         create(const std::string& name, const Date& birthday, const Address& addr);
    5 };

        支持Interface class 接口的那个具体类必须被定义出来,而且真正的构造函数必须被调用。一切都在virtual构造函数实现码所在的文件中。

     1 class RealPerson: public Person {
     2  public:
     3       RealPerson(const std::string& name, const Date& birthday, const Address& addr)
     4           :theName(name), theBirthDate(birthday), theAddress(addr)
     5       {}
     6       virtual ~RealPerson() {}
     7       std::string name() const;
     8       std::string birthDate() const;
     9       std::string address() const;
    10       //...
    11  private:
    12       /*实现条目*/
    13       std::string theName;
    14       Date theBirthDate;
    15       Address theAddress;
    16 
    17  };
    18 
    19 std::tr1::shared_ptr<Person> Person::create(const std::string& name,
    20      const Date& birthday,
    21      const Address& addr)
    22 {
    23     return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday, addr));
    24 }

    RealPerson示范实现了Interface class的两个常见机制:
      (1)从Interface class继承接口规格,然后实现出接口所覆盖的函数.
      (2)实现法涉及多重继承。

    5.Handle classes 和Interface classes解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性。但是存在的缺点是:它使你在运行期丧失若干速度,又让你为每个对象超额付出若干内存。

    具体如下:
         在Handle classes身上,成员函数必须通过implementation pointer 取得对象数据。那会为每一次
    访问增加一层间接性。而每一个对象消耗的内存数量必须增加implmentation pointer的大小。最后,
    implementation pointer必须初始化。指向一个动态分配得来的implementation object,所以你
    将蒙受因动态内存分配(及后的释放动作)而来的额外开销,以及遭遇bad_alloc异常(内存不足)
    的可能性。

         Interface classes,由于每个函数都是virtual,所以你必须为每次函数调用付出一个间接跳跃
    成本。此外Interface class派生的对象必须内含一个vptr,这个指针可能会增加放对象所需的内存
    数量---实际取决于这个对象除了interface class之外是否还有其他virtual函数来源。

    ------------------------------------------------------------------------------------------------------------------------------------------

    总结:

     支持“编译依存性最小化”的一般构想:相依于声明式,不要相依与定义式。基于次构想的两个手段是Handle classes和Interface classes。

     程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。


    声明:全文文字都是来源《Effective C++》 3th。这里所写都是自己的读书的时候梳理做的笔记罢了。希望对他人有用,那是我的荣幸。

    =======================================================================
    所有内容都是用BSD条款。 Copyright (C) by CloudFeng.
  • 相关阅读:
    服务器端口
    Format(const wchar_t *,...)”: 不能将参数 1 从“const char [3]”转换为“const wchar_t *”.
    图片格式
    CreateEx
    电力谐波
    [OGeek2019]babyrop
    Simple Inject
    [GXYCTF2019]BabySQli
    [CISCN2019 华北赛区 Day2 Web1]Hack World
    极客大挑战2019
  • 原文地址:https://www.cnblogs.com/cloudfeng/p/4360909.html
Copyright © 2020-2023  润新知