本文要写的是近期做项目过程中遇到的一个语法方面的小问题,下文从问题描述,解决思路以及总结这样的三段式描述来记录一下,以为备忘。
问题描述很简单。在实现一个模块的过程中,在头文件x.h中定义了class B, class B继承自class A; class A定义在y.h中;为了避免两个头文件之间不同数据结构定义之间的复杂依赖导致的问题,在头文件x.h中使用了对class A的前置声明,在源文件x.cxx中包含了头文件x.h和y.h。
x.cxx:
#include "x.h"
#include "y.h"
use class B;
x.h:
#ifndef X_H_INCLUDED
#define X_H_INCLUDED
class A;
class B:public A{
...
};
#endif
y.h:
#ifndef Y_H_INCLUDED
#define Y_H_INCLUDED
class A{
...
};
#endif
编译报错:
x.h:error: invalid use of undefined type struct A
x.h:error: forward declaration of struct A
解决思路:
我试图简单地调换了一下两个头文件在x.cxx中的包含顺序,编译通过。那么这里的问题在哪儿呢?看看将x.h和y.h分别在x.cxx中手动预处理之后会发生什么样的剧情。有两种情形:
1)如果x.h在y.h之前包含,我们得到与处理后的x.cxx的简要描述如下:
class A;
class B:public A{};
class A{};
use class B;
2)如果y.h在x.h之前包含,我们得到预处理后的x.cxx的简要描述如下:
class A{};
class A;
class B:public A{};
use class B;
不难看到,编译报错的原因在于派生类class B在定义之前编译器没有看到基类class A的定义,由于没有基类的定义,就无法为派生类分配对象。这就是两个头文件不同的包含顺序会导致正确和错误两种编译结果的本质原因。
总结:
从技术层面上,在大型软件中,模块化编程是常用的方式,特别在OO程序中,跨头文件的派生类的定义是比较多的;与此同时,就像我在之前的博文中提到的,在头文件中使用前置声明是一个很好的编程习惯。两者结合的时候,我们需要特别注意源文件中的头文件包含顺序,避免出现在定义派生类时没有基类定义的错误。
从解决问题的方法学方面,我的感觉是,解决问题的过程就是一个先将问题进行简化和抽象,然后再抽丝剥茧分析问题、按图索骥找到解决方案的过程,牢牢掌握已知的线索,逐渐还原问题的本质。简单问题如此,复杂问题更是如此。
问题描述很简单。在实现一个模块的过程中,在头文件x.h中定义了class B, class B继承自class A; class A定义在y.h中;为了避免两个头文件之间不同数据结构定义之间的复杂依赖导致的问题,在头文件x.h中使用了对class A的前置声明,在源文件x.cxx中包含了头文件x.h和y.h。
x.cxx:
#include "x.h"
#include "y.h"
use class B;
x.h:
#ifndef X_H_INCLUDED
#define X_H_INCLUDED
class A;
class B:public A{
...
};
#endif
y.h:
#ifndef Y_H_INCLUDED
#define Y_H_INCLUDED
class A{
...
};
#endif
编译报错:
x.h:error: invalid use of undefined type struct A
x.h:error: forward declaration of struct A
解决思路:
我试图简单地调换了一下两个头文件在x.cxx中的包含顺序,编译通过。那么这里的问题在哪儿呢?看看将x.h和y.h分别在x.cxx中手动预处理之后会发生什么样的剧情。有两种情形:
1)如果x.h在y.h之前包含,我们得到与处理后的x.cxx的简要描述如下:
class A;
class B:public A{};
class A{};
use class B;
2)如果y.h在x.h之前包含,我们得到预处理后的x.cxx的简要描述如下:
class A{};
class A;
class B:public A{};
use class B;
不难看到,编译报错的原因在于派生类class B在定义之前编译器没有看到基类class A的定义,由于没有基类的定义,就无法为派生类分配对象。这就是两个头文件不同的包含顺序会导致正确和错误两种编译结果的本质原因。
总结:
从技术层面上,在大型软件中,模块化编程是常用的方式,特别在OO程序中,跨头文件的派生类的定义是比较多的;与此同时,就像我在之前的博文中提到的,在头文件中使用前置声明是一个很好的编程习惯。两者结合的时候,我们需要特别注意源文件中的头文件包含顺序,避免出现在定义派生类时没有基类定义的错误。
从解决问题的方法学方面,我的感觉是,解决问题的过程就是一个先将问题进行简化和抽象,然后再抽丝剥茧分析问题、按图索骥找到解决方案的过程,牢牢掌握已知的线索,逐渐还原问题的本质。简单问题如此,复杂问题更是如此。