zoukankan      html  css  js  c++  java
  • 文件间的编译依存关系 分类: C/C++ 2015-06-10 15:37 34人阅读 评论(0) 收藏

           关于头文件的编译,在我另一篇转载的博客中有很详细的说明,在这里不再多言。

           现在直接比较以下两种类的实现方式:

    //头文件1
    #include <string>
    #include "date.h"
    #include "address.h"
    
    using namespace std;
    
    class Person{
    public:
    	Person(const std::string& name,const Date& birthday,const Address& addr);
    	string name()const;
    	string birthDate()const;
    	string address()const;
    	...
    private:
    	string theName;
    	Date theBirthDate;
    	Address theAddress;
    };
    
    //头文件2
    #include <string>
    //注意,这里的string不是class,它本身其实是一个宏定义
    //对C++标准库当中定义的类或函数在了解有限的情况下,个人认为不应当随意继承或更改
    class Date;
    class Address;
    
    using namespace std;
    
    class Person{
    public:
    	Person(const std::string& name,const Date& birthday,const Address& addr);
    	string name()const;
    	string birthDate()const;
    	string address()const;
    	...
    private:
    	string theName;
    	Date theBirthDate;
    	Address theAddress;
    };

          以上两个头文件,其类定义是完全相同的,不同之处在于类的声明方式,头文件1中直接在类定义包含进头文件中,头文件2则使用了前置声明的方式。对于这两种方式前者是优于后者的,原因如下:

          因为采用#include方式包含其实类似于宏展开,把一个头文件里的东西copy到另一个文件当中,的确,头文件不会直接参与编译,但头文件要不要编译呢?答案是肯定的,不编译的话如果有错误谁负责?头文件的编译其实是在包含它的源文件当中,用于IDE的都知道,一旦源文件当中的某一个字符更改了,那源文件就会重新编译。一旦Date或Address中的接口或数据成员改变,那包含它的所有源文件就要重新编译。而头文件2中的方式则不会引起这种效果,因为头文件2中只有声明,声明只是告诉编译器这个符号的存在。

          这就是所谓的编译依存关系,一个头文件更改会让所有包含它的源文件重新编译。

          那头文件2是否就完美了呢?其实不然,当class Person的接口或成员变量改变的时候,包含头文件2的源文件同样需要重新编译。有什么办法可以进一步降低编译依存关系呢?当我只想改变Person的实现或Person的成员变量而不改变接口的时候,如何避免包含头文件2的源文件重新编译?

    //头文件3
    #include <string>
    class Date;
    class Address;
    class PersonImpl;
    
    using namespace std;
    
    class Person{
    public:
    	Person(const std::string& name,const Date& birthday,const Address& addr);
    	string name()const;
    	string birthDate()const;
    	string address()const;
    	...
    private:
    	std::tr1::shared_ptr<PersonImpl> pImpl;
    };
    
    //头文件3对应的源文件
    #include "Person.h"
    #include "PersonImpl.h"
    Person::Person(const std::string& name,const Date& birthday,
    			const Address& addr)
    	:pImpl(new PersonImpl(name,birthday,addr))
    {
    }
    string Person::name()const
    {
    	return pImpl->name();
    }

           Person和PersonImpl的内容几乎是一模一样的,我把PersonImpl理解为Person的影子,Person的所有操作都是调用PersonImpl的对应操作,会包含#include"PersonImpl"的源文件有且仅有Person类,PersonImpl的改变仅仅只会影响到Person的源文件。Person只含一个PersonImpl的指针,指针或引用的定义只需要相应类的声明式,而定义式不是必须的。只要PersonImpl的接口不变,那么Person的接口也不必改变,Person不改变,那就不会引起编译依存效应。

          这种编程技法叫pimpl idion (pimpl 是“point to implementation”)的缩写。接口与实现完全分离!

          另一种方法是采用抽象类的方式实现,后绪补充!

          本文所有内容均来自《Effective C++》!


  • 相关阅读:
    5-1 Leetcode中和链表相关的问题
    4-7 带有尾指针的链表:使用链表实现队列
    4.6 使用链表实现栈
    4.5 链表元素的删除
    4.4 链表的遍历、查询和修改
    4.3 为链表设置虚拟头结点dummyhead
    4.2在链表中添加元素
    4.1链表
    mybatis 力量操作参数为List的非空校验
    linux 运行和停止jar的shell 脚本
  • 原文地址:https://www.cnblogs.com/zclzqbx/p/4687125.html
Copyright © 2011-2022 走看看