如何使用`boost :: variant`递归XML结构避免不必要的副本

时间:2017-01-02 21:02:59

标签: c++ c++11 vector boost variant

让我参考关于解析的boost精神教程中的the following example 一个"迷你XML"数据结构。我的问题实际上并没有任何关系 精神,它真正关于boost::variant,并提高效率"递归"变种 结构。

以下是代码:

struct mini_xml;

typedef
    boost::variant<
        boost::recursive_wrapper<mini_xml>
      , std::string
    >
mini_xml_node;

struct mini_xml
{
    std::string name;                           // tag name
    std::vector<mini_xml_node> children;        // children
};

在教程中,他们继续展示如何适应&#34; struct mini_xmlboost::fusion, 然后编写将数据加载到其中的精神语法。

然而,最近我发现在这个例子中存在一个微妙的问题,可能会导致很大的开销。

问题是variant类型mini_xml_node在这个例子中不是无法移动的构造。原因是它包含boost::recursive_wrapperrecursive_wrapper<T> 始终表示堆分配的T实例,并且它没有空状态。当一个 移动了包含variant的{​​{1}},新的动态分配和新的recursive_wrapper 是从旧T构建的移动 - 我们不能简单地取得旧T的所有权,因为这会使旧变体处于空状态。 (在我尝试实现自己的变体类型之后,我只调查了这一点 - 这确实是T所做的事情,并且在这个例子中确实不是无法移动的构造。)

因为boost::variant不是no-throw move可构造的,所以容器mini_xml_node将位于&#34;慢速路径&#34; - 当它使用std::vector<mini_xml_node> children时,它会选择复制元素 而不是移动它们。例如,每次矢量容量增加时都会发生这种情况。当我们复制std::move_if_noexcept时,我们会复制其所有孩子。

因此,例如,如果我想从磁盘解析一个深度为mini_xml_node和分支因子d的xml树,我估计我们最终将复制树的每个叶子{{{ 1}}次,因为推回到向量b次导致重新分配大约d * log b次,并且它将发生在叶子的每个祖先。

我现在没有真正的应用程序我关心这个开销,但我很容易想象 我可能。例如,我可能想要使用精神为一些小型实用程序编写一个高性能的解析器 这将检查,例如数据文件具有某种形式,或者计算某种形式的统计数据。然后呢 很可能是解析时间占据了实用程序的整体运行时间,因此这些副本可能对性能至关重要。

问题是,更改代码以防止这些副本发生的最简单,最干净的方法是什么?我想到了一些方法 但也许你看到了更好的方式。

  1. 快速而脏:编写一个包含b的包装类,但显式标记了移动构造函数 log b,即使它不是mini_xml_node。如果抛出异常,这将导致终止,但这应该是非常罕见的......

  2. 使用不noexcept的替代noexcept,而只使用std::vector。如果我理解正确,std::move_if_noexcept会这样做。权衡是它没有强大的异常安全性,但不清楚这是否真的存在问题。

  3. (这个实际上并不奏效。)添加move作为boost::vector的选项之一。

  4. boost::blank是其中一种值类型时,mini_xml_node具有适应其行为的特殊代码 - 它将使用 blank在默认构建和进行类型更改分配时作为自然空状态。

    然而,将boost::variant放入blank似乎并没有让我无法投掷移动构造 变体,即使它包含blank类型。使用提升boost::variant在coliru上进行测试:

    http://coliru.stacked-crooked.com/a/87620e443470b70c

    (也许这对将来boost::recursive_wrapper来说是一个有用的功能?我想要的是移动构造函数转移递归包装的所有者的所有权,并将旧的变体放在空白处state,并将移动构造函数标记为1.63。)

0 个答案:

没有答案