当前位置 博文首页 > Allen Roson:深入理解和探讨C++头文件包含问题

    Allen Roson:深入理解和探讨C++头文件包含问题

    作者:[db:作者] 时间:2021-07-09 09:42

    1.头文件包含

    1.1 系统头文件与自定义头文件

    使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:

    ?

    #include <stdio.h>

    这种形式用于引用系统头文件,编译器自带的头文件就是系统头文件,编译器编译时直接去系统目录找此头文件。

    ?

    #include "header.h"

    这种形式用于引用用户自定义头文件,它优先在包含当前文件的目录中搜索名为header.h 的文件, 如果没找到再到系统目录下查找。

    1.2 #pagma once与#ifndef

    为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。

    1.2.1 用法说明

    ?

    #ifndef用法

    xxxx.h中

    ?#ifndef??__SOMEFILE_H__

    #define?? __SOMEFILE_H__

    ?... ... // 声明、定义语句

    #endif

    ?

    ?

    #pragma once用法

    xxxx.h中

    #pragma once

    ?... ... // 声明、定义语句

    ?

    1.2.2 区别

    (1)

    #ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。

    ? ?? 当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。

    ? ?? 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。

    (2)

    #pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。

    其好处是,你不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。

    对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。

    另外,这种方式不支持跨平台!

    (3)

    #pragma once 方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式却不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。

    ?

    1.3 解决头文件互相包含的问题

    我们知道,当一个类(设类A)中包含另一个类(设类B)的对象时,必须在该文件中包含另一个类的头文件,如果两个类都互用到了对方的对象,理论上就要互相包含头文件,但是这样是无法通过编译的,其原因是它们的头文件互相包含了,你包含我,我又包含你,没完没了,如果没有使用代码防止头文件相互包含,那么编译器会报错如 :

    fatal error C1014: 包含文件太多: 深度 = 1024

    如果使用了如#pragma once这种手段防止头文件相互包含,那么编译器会报错如:

    error C4430 : 缺少类型说明符!

    ?

    1.3.1错误示范

    #ClassA.h

    #pragma once

    #include "ClassB.h"

    class ClassA

    {

    public:

    ??? ClassA();

    ??? ~ClassA();

    ??? ClassB m_objB;

    };

    ?

    #ClassA.cpp

    #include "stdafx.h"

    #include "ClassA.h"

    ClassA::ClassA()

    {

    }

    ClassA::~ClassA()

    {

    }

    ?

    ?

    #ClassB.h

    #pragma once

    #include "ClassA.h"

    ?

    class ClassB

    {

    public:

    ??? ClassB();

    ??? ~ClassB();

    ?

    ??? ClassA m_objA;

    };

    #ClassB.cpp

    #include "stdafx.h"

    #include "ClassB.h"

    ClassB::ClassB()

    {

    }

    ClassB::~ClassB()

    {

    }

    ?

    ?

    1.3.2 为什么会报错

    fatal error C1014: 包含文件太多: 深度 = 1024

    假设现在有2个类ClassA和ClassB是存在头文件相互包含的问题。

    //#include作用:在预处理阶段,编译器将源文件包含的头文件内容复制到包含语句(#include)处。

    ?

    /*假设程序先编译ClassA.cpp,在ClassA.cpp中包含了ClassA.h,此时把ClassA.h内容复制到到ClassA.cpp中替换掉

    #include "ClassA.h"这一行,ClassA.cpp 变成下面这个样子:*/

    ?

    /*这是#include "ClassA.h"被替换后的代码,注意由于#pragma once的作用,ClassA.h已经被包含了一次到ClassA.cpp中,

    不会再次被包含进去*/

    #include "ClassB.h"

    class ClassA

    {

    public:

    ClassA();

    ~ClassA();

    ?

    ClassB m_objB;

    };

    ?

    //后面的是ClassA.cpp原来的代码

    ClassA::ClassA()

    {

    }

    ClassA::~ClassA()

    {

    }

    ?

    /*同理可知此时ClassA.cpp中还有个#include "ClassB.h"也要被替换成ClassB.h的内容,替换之后,ClassA.cpp 变成下面这个样子:*/

    ?

    #include "ClassA.h"

    class ClassB

    {

    public:

    ??? ClassB();

    ??? ~ClassB();

    ??? ClassA m_objA;

    };

    ?

    class ClassA

    {

    public:

    ClassA();

    ~ClassA();

    ?

    ClassB m_objB;

    };

    ?

    //后面的是ClassA.cpp原来的代码

    ClassA::ClassA()

    {

    }

    ClassA::~ClassA()

    {

    }

    ?

    /*此时此刻,ClassA.cpp中的#include "ClassA.h"已经被替换了一次,ClassA.h的#include "ClassB.h"也被

    替换了一次到ClassA.cpp中,由于#pragma once的作用,ClassB.h和ClassA.h不会再被重复包含到ClassA.cpp

    中了,所以抛开编译器一些自动优化的处理不说,上面这个代码就可以算作是ClassA.cpp将要被编译的最终代码,

    那么就在就可以明确地看出来到底有什么错了:

    1、在ClassB的类声明中,出现了ClassA m_objA,由于ClassB的声明之前没有出现ClassA的声明,所以编译器无法

    识别这个类型,会报错:error C4430 : 缺少类型说明符 - 假定为 int。注意 : C++ 不支持默认 int

    2、在ClassA.cpp中,编译器能识别出来m_objB的类型是ClassB,但是在ClassB.cpp中,ClassA的声明在ClassB之前,会造成编译器不认识在ClassB.cpp中声明的m_objB这个成员变量的类型,那么编译器会认为ClassB.cpp中的m_objB的类型可能和ClassA.cpp中的m_objB的类型不一致,同一个类中同名的成员变量的类型如果不相同,那么编译器会报错:

    error C3646 : “m_objB”: 未知重写说明符*/

    ?

    1.3.3 解决方法

    1、如果在A类的定义中使用了B类的对象,那么就必须包含B类的头文件,只声明是不行的,因为要根据类的成员来给对象分配存储空间,如果不知道对象有哪些成员构成,就无法分配了。而B类中就不能使用A类的对象了,一旦使用就必须包含A类头文件,又造成相互包含的问题,这时可以用A类的指针,然后在B类头文件中加上A类的声明,指针占的字节数是固定的,不用担心程序不知道如何分配多大的内存。

    ?

    2、两个类中互相使用了对方的指针,这样的情况很简单,分别在各自的头文件中声明一下使用的类,而在各自的源文件中包含对方的头文件即可。

    ?

    3、如果A类继承了B类,那么在A类的头文件中必须要包含B类的头文件,只声明是不行的,道理同1.

    ?

    1.3.4 正确示例

    A类不用修改,B类改为如下代码即可:

    #ClassB.h

    #pragma once

    class ClassA;

    class ClassB

    {

    public:

    ??? ClassB();

    ??? ~ClassB();

    ??? ClassA *m_ptrA;

    };

    #ClassB.cpp

    #include "stdafx.h"

    #include "ClassB.h"

    #include "ClassA.h"

    ?

    ClassB::ClassB()

    {

    }

    ClassB::~ClassB()

    {

    }

    cs
    下一篇:没有了