c++ meta programming crash course

crash course 有一个很有意思的翻译,必知必会。之前一篇整理了一些基本的知识,这里整理一些很重要但是第一次可能常常忽略的点。会涉及到一些c/cpp程序如何进行compile的问题

redefination的问题

似乎在.h 文件开始的时候加上#ifndenf __XXXXX_H防止redefination是常识了。但有几次自己直接copy了旧的.h文件然后忘记改ifndenf后面的内容,这就比较尴尬了。还要注意,.h 文件不能进行定义只能进行声明。这个还是要从compiler的角度来思考,compile的基本单元是.cpp文件,所谓的#include,仅仅是把.h文件中的内容copy进来,这一点很关键。

有时候会把util的功能放在.h文件中,之后用static inline来声明每个函数,这里使用的static是说 “static means that the variable is only used within your compilation unit and will not be exposed to the linker” 基本上每个.cpp文件中都有一个对应的copy

如果一定要在.h文件中声明一些shareable的变量的话,需要使用extern关键字,这样就能避免redefination的问题。

参考

https://stackoverflow.com/questions/3837490/initializing-a-static-variable-in-header

https://stackoverflow.com/questions/5040525/static-variable-in-a-header-file

forward declaration

forward declaration是可以用来解决class/struct之间互相引用的问题。这种互相引用的问题有的时候是由于bad design引起的,有的时候是不得已而为之。如果是bad design 这个时候考虑是不是要对已有的功能进行拆分,比如之前是A需要引用B,然后B也需要引用A,拆分之后得到更底层的C,然后A和B都去引用C。或者想想看调整一下每个类所能执行的函数看看效果会不会更好。

实在需要互相引用的话,常遇到的错误是 invalid use of incomplete type 这个问题往往与循环引用相关,比如

//car.h
struct Car{
Wheel * w
}

//wheel.h
struct Weel{
Car *c
}

这个时候car.h需要知道wheel的定义,会加上#include <wheel.h> 但是wheel.h中也需要引用Car啊?如果这个时候在weel.h中也加上#include <Car.h> 那就会出现循环引用。如果使用forward declaration的话,就是在.h文件中不要引用上一层(这里Car对于Wheel是上一层)的类,在weel.cpp中去引用上一层的类然后进行如下声明struct Car;这个就是所谓的forward declaration。总结一下就是要保证.h文件中互相引用的拓扑排序,在.cpp文件中declare需要引用的struct或者class。想一下背后的原因也很容易理解,compilation unit是.cpp文件,因此在.cpp文件直接引用上层的.h文件不会造成循环。

自己遇到的一个tricky的问题是 A->B, B->A, B->C, C->A 这里的A->B表示A中有一个指针指向B。先不讨论design的问题,要是想让这个work了,需要在A.h 中 引用 B.h,在B.h中引用C.h,在B.cpp中引用A.h,在C.cpp中引用A.h,如果有一个地方弄错了都会造成编译问题。 比较重要的是不能在C.h中引用A.h和B.h,否则会出现循环引用的问题。

参考

https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes

https://www.gamedev.net/forums/topic/562671-invalid-use-of-incomplete-type-and-forward-declaration/

https://stackoverflow.com/questions/4757565/what-are-forward-declarations-in-c/4757718#4757718

template 要初始化

刚开始使用template的时候总是忘记要初始化,定义的template只是相当于declaration,只有把它和具体的底层类型结合起来,compiler才知道到底分配多少的空间。

推荐文章