boost::transform 对比 std::transform

boost::transform vs std::transform

根据下面的代码片段,我是否应该得出结论,std::transformboost::transform 更有效,因为前者使用的初始化和析构函数比后者少?

#include <algorithm>
#include <boost/range/algorithm.hpp>
class Ftor {
public:
    Ftor(const Ftor& rhs) : t(rhs.t)
    { std::cout << " Ftor : copy\n"; }
    Ftor(float rate) : t(rate)
    { std::cout << " Ftor : init\n"; }
    ~Ftor()
    { std::cout << "~Ftor : ...\n"; }
    float operator() (float x) { return x < t ? 0.0 : 1.0; }
private:
    float t;
};

typedef std::vector<float> vec_t;

int main (void) 
{
    vec_t arg(/*...*/);
    vec_t val(arg.size());
    float x = 1.0;
    /* Standard transform test */
    std::cout << "Standard transform:\n";
    std::transform(arg.begin(), arg.end(), val.begin(), Ftor(x));
    std::cout << "Boost transform:\n";
    /* Boost transform test */
    boost::transform(boost::make_iterator_range(arg.begin(), arg.end()),
                     val.begin(), Ftor(x));
}

输出:

Standard transform:
 Ftor : init
~Ftor : ...
Boost transform:
 Ftor : init
 Ftor : copy
~Ftor : ...
~Ftor : ...

标准转换使用 2 次调用。 Boost 变换使用 4 次调用。标准变换获胜。还是……?

附录

正如@sehe 所建议的那样,std::ref 每次调用 transform 时保存一个构造函数,而 boost::transform 仅使用一次调用。但是 std::ref 不能将临时参数作为参数。但是,传递 Ftor f(x) 就可以了,因为后者有一个明确的地址。

考虑在循环内调用 transform 时的 constructor/destructor 调用。我现在有两种选择:

std::cout << "with std::ref\n";
for (/*...*/) {
    x = ...;
    f = Ftor(x);
    boost::transform(arg, val.begin(), std::ref(f));
}

std::cout << "with temporary\n";
for (/*...*/) {
    x = ...;
    boost::transform(arg, val.begin(), Ftor(x));
}

输出:

with std::ref
 Ftor : init
 Ftor : init
 ...
~Ftor : ...
with temporary
 Ftor : init
 Ftor : copy
~Ftor : ...
~Ftor : ...
 Ftor : init
 Ftor : copy
~Ftor : ...
~Ftor : ...
 ...

我有一个gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4 使用或不使用 -O3 都会产生相同的结果。

constructor/destructor是否“贵”是相对于operator()来说的。它是最终产品,它将执行不太苛刻的数学运算,与上面的示例没有什么不同。

Complete example on coliru

允许STL算法复制它们的predicate/functors。这主要是因为它们是按值传递的。

你看到的是 boost 做了 一次 前向调用。

有问题吗?

通常不会。编译器非常擅长内联、复制省略和依赖分析。

很有可能,生成的代码最终完全相同。

Of course, adding the cout statements completely wrecks that. Compare generated code without the constructor/destructor side-effects spoiling it!

公平比较using no-side-effects generates identical code for the STL and Boost variants: http://paste.ubuntu.com/14544891/

修复

STL(和 boost 算法)的设计方式,如果需要,您可以通过引用显式传递仿函数。您为此使用 std::ref

Live On Coliru

#include <algorithm>
#include <vector>
#include <iostream>
#include <functional>
#include <boost/range/algorithm.hpp>

class Ftor {
  public:
    Ftor(const Ftor &rhs) : t(rhs.t) { std::cout << " Ftor : copy" << std::endl; } 
    Ftor(float rate) : t(rate)       { std::cout << " Ftor : init" << std::endl; } 
    ~Ftor()                          { std::cout << "~Ftor : ..."  << std::endl; } 
    float operator()(float x)        { return x; }  

  private:
    float t;
};

typedef std::vector<float> vec_t;

int main(void) {
    vec_t arg(190, 1), val(arg.size());

    {
        std::cout << "STL transform: " << std::endl;
        Ftor f(1.0);
        std::transform(arg.begin(), arg.end(), val.begin(), std::ref(f));
    }
    std::cout << "-----\n";

    {
        std::cout << "Boost transform: " << std::endl;
        Ftor f(1.0);
        boost::transform(arg, val.begin(), std::ref(f));
    }
    std::cout << "-----\n";
}

版画

STL transform: 
Ftor : init
~Ftor : ...
-----
Boost transform: 
Ftor : init
~Ftor : ...
-----

注意 无论如何,在容器上使用范围算法并构建范围 非常非常讽刺 boost::make_iterator_range(arg.begin(), arg.end())而不是仅仅使用 arg:

boost::transform(arg, val.begin(), Ftor(x));