保证检测临时>命名点

时间:2015-06-02 11:38:27

标签: c++ c++11 move lazy-evaluation

假设您编写了一个包含一些操作的矩阵类:

class matrix
{
public:
    double operator()(size_t i, size_t j) const;
    ...
};

matrix operator*(const matrix &lhs, const matrix &rhs);
...

推迟评估某些矩阵表达式是有意义的: m0 * m1 * m2 * m3 * m4 (这是一系列的四个operator*调用)可以从使用dynamic-programming matrix chain multiplication algorithm;非常常见的 m0 * m1 t 有一个very efficient dgemm implementation,依此类推。

因此,将实际计算推迟到需要时才付出代价。这会将上述内容更改为:

class matrix
{
private:
    /*
    * Pointer to an abstract base class - either an actual matrix, 
    *    or an expression tree. */
    std::shared_ptr<matrix_imp> m_imp;

public:
    // Forces compaction - 
    double operator()(size_t i, size_t j) const;
    ...
};

/* Lazy; creates a matrix with an expression tree using the
*    internals of lhs and rhs. */
matrix operator*(const matrix &lhs, const matrix &rhs);
...

每个矩阵都包含一个指向基类对象的指针,该对象的范围可以从实矩阵到复杂的表达式树。每个操作都尝试使用对内部实现的最懒惰的更改来形成矩阵。有些操作别无选择,只能实际评估事物,压缩表达式树,并将内部实现设置为实际矩阵。

问题是,在实践中,这在非常常见的情况下造成了巨大的内存开销。假设您从文件中读取长窄矩阵 x = x p X q ,p&gt;&gt; q ,将 x t x 存储在变量中,并丢弃 x 。通过延迟评估,内存为 pq&gt;&gt; QQ 。将它们加载到循环中,这是一个严重的问题。 (当然,调用operator()的客户端代码在每次加载后都可以强制执行压缩,但是在没有算法验证的情况下要求这样做是丑陋且容易出错的。)

最初,我认为移动ctor是自动压缩的一个好点 - 它恰好是临时成为命名对象的点,而且它的命名对象会导致内存消耗增加,所以

matrix(matrix &&other); // <- Force compaction only here

似乎可以解决所有问题,例如,

auto res = // <- temp becoming named
    a * // temp
    b * // temp
    c + // temp
    2 * // temp
    d;

但可以指望吗?例如,考虑

matrix load_xtx(const string &f_name)
{
    matrix x = ...
    return x.t() * x; 
}

auto xtx = load_xtx("foo.hdf5"); // (*)

是禁止在(*)中使用与NRVO类似的编译器,只是为了构建它?即使不是,编译器可能会在其他情况下优化掉事物吗?

1 个答案:

答案 0 :(得分:2)

由于&#34;内部指针&#34;方法不能给出延迟评估所需的所有灵活性,C ++数值库使用的典型解决方案是定义实现惰性评估机制的专用类。旧的SO问题Lazy evaluation in C++及其最佳答案显示了此类设计的基础知识和一些示例代码。

虽然我不是专家,但我认为这个架构的好例子是数字库Eigen(here some details about its implementation)和Blitz ++,它们在很大程度上依赖于模板(我没有在网上找到更新的文档来说明其内部结构,但是this article描述了其引擎的某些部分,并且还提供了对表达模板&#34;技术的更广泛的概述。