正在做的项目多处使用了rapidxml.hpp来处理xml配置文件。但是一直以来有个奇怪的问题,其他同事可以编译通过的代码,在我的电脑上就编译不能通过,会在这个文件的这一行:
xml_node<Ch> *node = new(memory) xml_node<Ch>(type);
报错:
Error 25 error C2061: syntax error : identifier 'memory'
当时同事帮我发现了一个解决办法:把include这个文件的那一行放到第一行。这样可以编译,但是我们仍然不明真相。
上周为了提高编译速度,增加了预编译头文件。这样一来,头文件放到第一行的方法就不管用了。只能找到根本的原因,google之发现这里有真相:
原因是我本地代码为了做内存泄露检测重载了new,而前面的奇怪用法叫做placement new,这个用法一旦有重载new就会失效,从而报错。
知道了原因之后就要寻求解决办法,但是内存泄露检测不重载new还有什么方法呢?这个问题目前还没有研究出来。和另一个同事请教他说之前他的做法是把placement new给去掉。于是朝向这个目标努力。按照我的理解placement new和普通new的区别在于不分配内存,而是直接在自己管理的内存上调用构造函数,用完后不delete,而是显式调用析构。自己管理那块内存的分配和释放是另外写代码管理的。我想直接把这两处替换成原来的new和delete就好了。new的地方就是刚刚报错的那里,可是调用析构的代码却找不到,于是又看了看代码,发现不是我想的那么简单。
在rapidxml里面,使用placement new的原因不是为了提高频繁分配释放内存的效率,而是因为如果直接用new一个一个节点分别分配的话,节点分到的内存不连续,cache miss的可能性高,访问速度就下来了。所以采用了一开始直接挖一大块内存,然后从里面分配的方法。这些分配出来的节点,在整个xml处理的过程中都是不需要释放的,一直到最后,xml处理完不再需要的时候,直接把这一大块内存给释放掉。同事说以前做console的时候经常这么做。
这样一来,我想简单移除placement new就麻烦了,最后同事又给我建议了一个方法:本地把预编译头里的所有头文件都注掉。因为我们预编译头是后加的,所以只是加了这个东西,各个单独文件该包括的头文件还都包括着。于是终于通过编译了。
不过这个做法还是有后患,比如今天有人新加了文件,因为已经有了预编译头,所以他就没有再额外包含头文件了,于是我这里又编不过,只能自己找了要用的头文件加上。所以最最治本的方法还是解决为了检测内存泄露而重载new,以及重载new了之后placement new不能用的冲突。研究中。。。。。。
UPDATE 20130318:
今天又有人加了代码造成过百个编译错误,都在头文件。还好周一早上头脑比较清醒,意识到可以直接把预编译头放回来,然后把#include "rapidxml.hpp" 放到预编译头的第一个。早怎么没想到呢。