项目中要使用xml打包、解析协议,HQ指定了使用rapidxml--号称是最快的xml解析器。
功能很快完成了,但发现rapidxml为了追求性能,做了一些对用户来说并不友好的设计。下面来说一说:
给xml对象在添加节点时,不可添加临时变量
按照一般用法,使用如下方式添加节点:
rapidxml::xml_document<> doc; void addNode(std::string value) { rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "unregister_context"); doc.append_node(root); root->append_node(doc.allocate_node(rapidxml::node_element, "who_register", value.c_str())); }
但在rapidxml中这么写实有问题的,得这么写:
rapidxml::xml_document<> doc; void addNode(std::string value) { rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "unregister_context"); doc.append_node(root); root->append_node(doc.allocate_node(rapidxml::node_element, "who_register", doc.allocate_string(value.c_str()))); }
看出差别了吗?
待插入的值"变量value"是作为参数传递进来的,是临时变量。rapidxml为了追求极致性能,在append_node()函数中是直接通过指针来访问value变量的,并没有进行内存拷贝--因此rapidxml在这里提出了一个隐晦的前提条件:在xml对象doc的生命周期内,必须保证"变量value"能够被正常访问。
那么实际情况呢?
仔细检查一下,就会发现"变量value"是临时变量,在addNode()函数执行完毕后就会被销毁;此时xml对象rapidxml::xml_document<> doc内部保存的值还指向“变量value”的内存地址,而该地址已经不可用了。因此在访问xml对象时就会发生segment fault。
问题出现了,该怎么解决?我们是无法控制临时变量的生命周期的,因此只能对该变量进行拷贝。rapidxml已经提供了该功能,这就是allocate_string()函数。该函数在rapidxml对象内部的内存池中为我们的变量申请了一份内存,然后将“变量value”的值拷贝过去;由于是xml对象自己维护该内存池,因此就不存在变量地址失效的问题了。
以上情况仅针对allocate_node()待插入的值是临时变量这种情况;如果用户能保证待插入变量的生命周期、或者是常量,应该不需要使用allocate_string()函数来分配内存了。例如:
rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "data_coming", "some data");
这里第三个参数"some data"是常量,生命周期等于整个程序的生命周期,因此就不用再为它分配内存了。
(ps:此种情况仅是推测,未做测试。)
在为xml对象添加节点时,请保证变量的生命周期!
总结:
rapidxml为了追求性能,减少内存拷贝,就尽可能的通过指针(内存地址)来访问用户的变量;这就对用户提出了要求:必须保证变量的生存周期,如果变量被销毁了,rapidxml就会访问无效的内存地址,引发不可控的后果。
而对于普通用户来说,一般都比较少注意到这个细节。
为了追求性能,而牺牲了一定的可用性。这种设计是否合理?