文档章节

如何使用前置声明取代包括头文件

赵西元
 赵西元
发布于 2013/10/06 15:54
字数 1356
阅读 66
收藏 0
这篇文章很大程度是受到Exceptional C++ (Hurb99)书中第四章 Compiler  Firewalls and the Pimpl Idiom  (编译器防火墙和Pimpl惯用法) 的启发,这一章讲述了减少编译时依赖的意义和一些惯用法,其实最为常用又无任何副作用的是使用前置声明来取代包括头文件。

Item 26 的Guideline - "Never #include a header when a forward declaration will suffice"

在这里,我自己总结了可以使用前置声明来取代包括头文件的各种情况和给出一些示例代码。

首先,我们为什么要包括头文件?问题的回答很简单,通常是我们需要获得某个类型的定义(definition)。那么接下来的问题就是,在什么情况下我们才需要类型的定义,在什么情况下我们只需要声明就足够了?问题的回答是当我们需要知道这个类型的大小或者需要知道它的函数签名的时候,我们就需要获得它的定义。

假设我们有类型A和类型C,在哪些情况下在A需要C的定义:

  1. A继承至C
  2. A有一个类型为C的成员变量
  3. A有一个类型为C的指针的成员变量
  4. A有一个类型为C的引用的成员变量
  5. A有一个类型为std::list<C>的成员变量
  6. A有一个函数,它的签名中参数和返回值都是类型C
  7. A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
  8. A有一个函数,它的签名中参数和返回值都是类型C(包括类型C本身,C的引用类型和C的指针类型),并且它会调用另外一个使用C的函数,代码直接写在A的头文件中
  9. C和A在同一个名字空间里面
  10. C和A在不同的名字空间里面

1,没有任何办法,必须要获得C的定义,因为我们必须要知道C的成员变量,成员函数。

2,需要C的定义,因为我们要知道C的大小来确定A的大小,但是可以使用Pimpl惯用法来改善这一点,详情请
看Hurb的Exceptional C++。

3,4,不需要,前置声明就可以了,其实3和4是一样的,引用在物理上也是一个指针,它的大小根据平台不同,可能是32位也可能是64位,反正我们不需要知道C的定义就可以确定这个成员变量的大小。

5,不需要,有可能老式的编译器需要。标准库里面的容器像list, vector,map,
在包括一个list<C>,vector<C>,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。

6,不需要,只要我们没有使用到C。

7,需要,我们需要知道调用函数的签名。

8,8的情况比较复杂,直接看代码会比较清楚一些。

            C& doToC(C&);
            C& doToC2(C& c)  {return doToC(c);};

从上面的代码来看,A的一个成员函数doToC2调用了另外一个成员函数doToC,但是无论是doToC2,还是doToC,它们的的参数和返回类型其实都是C的引用(换成指针,情况也一样),引用的赋值跟指针的赋值都是一样,无非就是整形的赋值,所以这里即不需要知道C的大小也没有调用C的任何函数,实际上这里并不需要C的定义。

但是,我们随便把其中一个C&换成C,比如像下面的几种示例:

            1.
                C& doToC(C&);
            C& doToC2( C c{return doToC(c);};
                
                2.
                C& doToC( C);
                C& doToC2(C& c) {return doToC(c);};

                3.
                 doToC(C&);
                C& doToC2(C& c) {return doToC(c);};

                4.
                C& doToC(C&);
                 C doToC2(C& c) {return doToC(c);};

无论哪一种,其实都隐式包含了一个拷贝构造函数的调用,比如1中参数c由拷贝构造函数生成,3中doToC的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数,所以以上无论那种情形都需要知道C的定义。

9和10都一样,我们都不需要知道C的定义,只是10的情况下,前置声明的语法会稍微复杂一些。

最后给出一个完整的例子,我们可以看到在两个不同名字空间的类型A和C,A是如何使用前置声明来取代直接包括C的头文件的:

A.h

#pragma once

#include <list>
#include <vector>
#include <map>
#include <utility>

     //不同名字空间的前置声明方式
namespace test1
{
          class C;
}




namespace test2
{   
       //用using避免使用完全限定名
    using test1::C;
    
    class A 
    {
    public:
                C   useC(C);
            C& doToC(C&);
            C& doToC2(C& c) {return doToC(c);};
                         
    private:
            std::list<C>    _list;
            std::vector<C>  _vector;
            std::map<C, C>  _map;
            C*              _pc;
            C&              _rc;
    
    }
;
}


C.h

#ifndef C_H
#define C_H
#include <iostream>

namespace test1
{
          
    class C
    {
    public:
           void print() {std::cout<<"Class C"<<std::endl;}
    }
;

}


#endif  //  C_H

本文转载自:http://blog.csdn.net/rogeryi/article/details/1439597

赵西元
粉丝 12
博文 29
码字总数 12172
作品 0
大连
私信 提问
C++ 前置声明

问题: 最近遇到了两个类A、B相互调用的情况,于是想当然的在两个类A和B的头文件中 #include 了所需的头文件,当然结果编译报错了。为什么呢,A需要B,B需要A,形成了循环,违反了程序的确定...

006玩命
2016/08/25
0
0
​C语言的不完整类型和前置声明

声明与定义(Declaration and Definition) 开始这篇文章之前,我们先弄懂变量的declaration和definition的区别,即变量的声明和定义的区别。 一般情况下,我们这样简单的分辨声明与定义的区...

C_Geek
2015/11/03
0
0
C++前置声明的一个好处与用法

在C++ GUI Qt编程中有下面的一个头文件: #ifndef FINDDIALOG_H define FINDDIALOG_H include <QDialog> class QCheckBox;//为什么要进行前置声明?class QLabel;class QLineEdit;class QPush......

无奈的初弦
2014/02/08
0
0
C++前置声明

1.用来解决两个类之间的强耦合问题,例如: 上面编译不能通过,可以通过前置声明,然后在需要用到的类中定义该前置声明的指针或引用,然后在指针或引用实例化的地方包含前置声明类的头文件。...

Ne0o0
2016/03/24
37
0
头文件

写的很好的文章,转载自http://www.learncpp.com/cpp-tutorial/19-header-files/ 在程序中,.cpp扩展的文件并不是唯一一种常见的文件。另一种文件称为头文件,有时被称为include file。都文件...

长平狐
2012/11/12
477
0

没有更多内容

加载失败,请刷新页面

加载更多

分布式架构 共享session的常见解决方案

在使用分布式架构时,会遇到分布式架构常见的几个问题: 分布式事务、接口幂等性、分布式锁和分布式 session。 分布式session 一、什么是session 浏览器在访问一个web服务的时候,会在浏览器...

太猪-YJ
24分钟前
2
0
Android java.lang.NoClassDefFoundError:failed resolution of :Lorg/apache/http/ProtocolVersion解决方案

这是GooglePlay Services方面的一个bug,在修复之前,可以通过将此添加到AndroidManifest.xml内部<application>标签: <uses-library android:name="org.apache.http.legacy" android:requir......

醉雨
29分钟前
0
0
蚂蚁金服 Service Mesh 落地实践与挑战 | GIAC 实录

本文整理自 GIAC(GLOBAL INTERNET ARCHITECTURE CONFERENCE)全球互联网架构大会,蚂蚁金服平台数据技术事业群技术专家石建伟(花名:卓与)的分享。分享基于 Service Mesh 的理念,结合蚂蚁...

SOFAStack
34分钟前
1
0
Java跨平台原理

此篇博文主要源自网络xiaozhen的天空的博客:http://xiaozhen1900.blog.163.com/blog/static/1741732572011325111945246/   1、是么是平台 Java是可以跨平台的编程语言,那我们首先得知道什...

vinci321
34分钟前
1
0
分享 KubeCon 2019 (上海)关于 Serverless 及 Knative 相关演讲会议

有幸参加了 KubeCon 2019 上海大会,并参加了 Knative 及 Serverless 相关的几场分享会,收获满满。这里简单介绍一下各个演讲主题的主要内容。详细的演讲主题文档可以在KubeCon官方获取:htt...

阿里云官方博客
43分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部