最近项目编译的时候遇到了Circular Dependencies的问题。
说到Circular Dependencies,简而言之就是头文件A include了头文件B,头文件B又引用了头文件A。
虽说我们平时都会稍微注意着点,不让这么明显的情况发生,但是如果文件多了,引用层次多了,还是可能会发生这样的问题。
有两种思路来尝试解决这个问题。
前置声明
我们知道,只要你没有用到struct或者class的具体内容,只是把它们的指针传来传去,那是可以用前置声明的。比如这样:
1 | // File: HeaderA.h |
可以看到我上面列举了三种前置声明的写法。
- 第一种肯定不行,因为编译器需要知道Struct的大小,但是前置声明并没有具体的struct信息,所以不能提供大小信息,编译不会通过。
- 能够编译通过,但是这样子就是一个非UE4管理的指针,需要自己负责它的内存管理,就是需要自己new和delete
- 不能编译通过,因为uproperty不支持struct指针
所以如果采用前置声明方案,就只能够使用struct指针 + 手动管理内存了。没有了UE4的内存管理,写起代码来很是麻烦。
管理头文件
既然要尽量避免struct的前置声明,那么就必须把struct所在的头文件包含进来。
回过头来想一想,之前为什么会产生循环依赖呢?这八成是因为我们经常把struct和class写在同一个头文件中。struct我们经常只是使用「内联类型」(比如int,float,bool)来作为成员变量的类型;而对于class,有很大概率其成员变量的类型是另一个自定义类。
1 | struct MyStruct |
如果在头文件里面包含了另一个定义了自定义类的头文件,那么就很有可能最后会组成一个很复杂的依赖链。
所以,如果实在有必要引入struct所在的头文件,那么我们要尽量避免这个头文件里面定义了另一个类。
具体地来说,写代码的时候可以有以下规则:
- struct放在专门的头文件中,并且这个头文件里面不可以有class的定义,避免在头文件中include该文件的时候会include到类的定义,从而又依赖了其他的复杂类型
- 同个功能模块的struct可以放在同一个头文件中定义,但是不同模块的strcut最好放在不同的头文件。这是为了避免有太多地方引用头文件(设想一下整个项目只有一个Header.h头文件,里面定义了所有的struct,被各个cpp文件包含),导致一改动头文件里面的一点内容,就会导致大量重新编译的问题。
That’s All.
最后祝各位:身体健康。