zoukankan      html  css  js  c++  java
  • Effective C++ 条款31 将文件中间的编译依存关系降至最低

    (参考自http://www.cnblogs.com/jerry19880126/p/3551836.html)

    1. 与java语言不同,对于C++,在创建类的对象时,编译器必须要在编译期间看到类定义,以确定要分配的内存大小,因此要定义一个类对象,文件常常要include包含类定义的头文件,这就引发了一个问题:如果头文件中的类定义(数据或接口)发生改变,那么include该头文件的所有文件都需要重新编译,这就增加了开发负担.

    2.使用Handle class

    1中问题在java中并不存在,因为java是基于指针(表面是引用)管理动态内存分配的对象的,而创建一个指向某类的指针只需要类的前置声明即可.由此启发,C++也可以采用这种方式"将对象实现细目隐藏于指针背后",例如一个名为Demo的类含有大量数据成员和函数成员,那么可以创建一个realDemo类存有这些成员,而Demo类只包含指向realDemo的指针,如下:

    //"RealDemo.h"
    #ifndef REAFDEMO
    #define REALDEMO
    class RealDemo{
    public:
        int fun1();
        int fun2(int num);
        ...
    private:
        ...
    }
    #endif REALDEMO
    View Code
    //"RealDemo.cpp"
    #include "RealDemo.h"
    int RealDemo::fun1(){
        ...
    }
    int RealDemo::fun2(int num){
        ...
    }
    ...
    View Code
    //"Demo.h"
    #ifndef DEMO
    #define DEMO
    class RealDemo;
    class Demo{
    public:
        int fun1();
        int fun2(int num);
        ...
    private:
        RealDemo* ptrEntity;
    }
    #endif
    View Code
    //"Demo.cpp"
    #include "Demo.h"
    #include "RealDemo.h"
    int Demo::fun1(){
        ptrEntity->fun1();
    }
    int Demo::fun2(int num){
        ptrEntity->fun2(num);
    }
    ...
    View Code

        从以上可以看出,"Demo.h"中只包含了RealDemo的类声明(只有类声明便足以创建指针),而"Demo.cpp"包含了"RealDemo.h",这样一旦RealDemo任何改变,只有"Demo.cpp"需要重新编译,"Demo.h"不需要重新编译,任何include"Demo.h"的文件也不再需要重新编译,也就是说Demo相当于一个"缓冲层",对RealDemo改变引起的冲击只蔓延到Demo.cpp,对Demo.h无影响,因为Demo.h只包含了RealDemo的声明.

        以上方法源于以下策略:

        "如果使用object references 或 object pointers可以完成任务,就不要使用objects":创建一个类对象需要该类定义,而创建一个类指针或引用只需要该类声明.

        "如果能够,尽量以class声明式替换class定义式":定义一个有类类型的函数,在这之前只需要该类声明即可,即使以by-value方式传递参数也是一样,但是调用该函数的时候仍然需要看到类定义,这样可以"将提供class定义式(通过#include完成)的义务从"函数声明所在"之头文件转移到"内含函数调用"之客户文件",从而将"并非真正必要之类型定义"与客户端之间的编译依存性去掉,因为未必所有客户都会调用那个函数.

        此外,如果要使用Demo和RealDemo的声明,一般不手动声明,而是将它们放在一个只包含声明的.h文件中,使用时include即可,例如DemoReal类的声明置于"Demofwd.h" 头文件中.(只含声明的那个头文件命名为"Demofwd.h",取法效仿于C++标准库中的<iosfwd>,<iosfwd>内包含iostream各组件的声明式,其对应定义分布在若干不同的头文件内,包括<iostream>,<fstream>,<sstream>,<streambuf>)

    3. "使用Interface class"

        Iterface cass是另一种制作Handle class的办法,其基本思想是令Demo成为一种特殊的abstract base,称为Interface class.该Interface class不包含任何数据成员,而在其派生类RealDemo中包含所有功能,如下:

    //"Demo.h"
    #ifndef DEMO
    #define DEMO
    class Demo{
    public:
        static Demo* createRealDemo(...);
        virtual int fun1()=0;
        virtual int fun2(int)=0;
        ...
    private:
        ...
    }
    #endif
    View Code
    //"RealDemo.h"
    #ifndef REALDEMO
    #define REALDEMO
    #include"Demo.h"
    class RealDemo{
    public:
        virtual int fun1();
        virtual int fun2(int num);
        ...
    private:
        ...
    }
    View Code
    //"RealDemo.cpp"
    #include"RealDemo.h"
    #include"Demo.h"
    Demo* Demo::createRealDemo(...){
        return new RealDemo(...);
    }
    int RealDemo::fun1(){
        ...
    }
    int RealDemo::fun2(int num){
        ...
    }
    View Code

    要创建RealDemo,通过调用Demo的static成员creativeRealDemo创建RealDemo并返回一个指向Demo的指针,利用多态性操纵Demo*而实现对RealDemo的操纵.如果过RealDemo发生改变,需要重新编译的只有include"RealDemo.h"的RealDemo.cpp,"Demo.h"不需要重新编译,任何include"Demo.h"的cpp文件也不需要重新编译.

    Interface Class通过将具体实现放在派生类中,使用时include基类头文件,利用多态性通过操纵基类指针操纵派生类对象,因为只有派生类的实现文件include派生类的.h文件,因此对派生类做的任何改变只会影响到派生类的.h文件和.cpp文件.

    4. 以上两种方法核心思想都是在中间加一层"代理类",对底层类的操作经由代理类进行,由于其他文件直接使用"代理类"而基础不到底层类,从而对达到改变底层类只影响"代理类"的效果.Handles Class通过将Demo类转为代理类,用于管理底层类RealDemo的指针,Interface Class则是将Demo类转为抽象基类作为代理类,再利用多态性和动态绑定的方法间接操纵底层类RealDemo.

        这两种方法都实现了降低文件之间的编译依存性的功能,但是必然会有副作用:

        1). Handles Class成员函数必须通过implementation pointer取得对象数据,这为数据访问增加了一层间接性,此外每个对象也增加了一个implentmtation pointer所占用的大小.此外implementation必须必须初始化而指向一个动态分配的implementation object,因此将蒙受动态内存分配所带来的开销,以及遭遇bad_alloc异常(内存不足)的可能性.

        2). Interface Class由于每个函数(除了create函数)都是virtual函数,因此必须为每个函数调用付出一个间接跳跃("indirect jump")成本,此外vptr(虚函数表指针)可能会使每个对象增加所占内存.

        3). 此外,这两种方式都无法将函数inline,因为要将函数inline需要函数实现细节,而这两种方式正是用来隐藏函数细节的.

  • 相关阅读:
    401. Binary Watch
    46. Permutations
    61. Rotate List
    142. Linked List Cycle II
    86. Partition List
    234. Palindrome Linked List
    19. Remove Nth Node From End of List
    141. Linked List Cycle
    524. Longest Word in Dictionary through Deleting
    android ListView详解
  • 原文地址:https://www.cnblogs.com/reasno/p/4794114.html
Copyright © 2011-2022 走看看