From the snippet below should I conclude that std::transform
is to be preferred over boost::transform
as probably more efficient because the former uses fewer initialisations and destructors than the latter?
#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));
}
Output:
Standard transform:
Ftor : init
~Ftor : ...
Boost transform:
Ftor : init
Ftor : copy
~Ftor : ...
~Ftor : ...
Standard transform uses 2 calls. Boost transform uses 4 calls. Standard transform wins. Or does it...?
ADDENDUM
As @sehe suggested std::ref
saves one constructor per call to transform
with which boost::transform
uses only one call. But std::ref
cannot take a temporary as argument. But then, passing Ftor f(x)
is fine since the latter has a definite address.
Accounting for the constructor/destructor calls when transform is called inside a loop. I've two options with boost now:
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));
}
Output:
with std::ref
Ftor : init
Ftor : init
...
~Ftor : ...
with temporary
Ftor : init
Ftor : copy
~Ftor : ...
~Ftor : ...
Ftor : init
Ftor : copy
~Ftor : ...
~Ftor : ...
...
I've got a gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
with or without -O3
yields the same results.
Whether the constructor/destructor is "expensive" is relative to the operator()
. It a final product, it is going to perform not too demanding math, not dissimilar to the example above.
STL algorithms are allowed to copy their predicate/functors. This mostly happens because they're passed by value.
What you see is that boost
does one more forward call.
Not usually. Compilers are very good at inlining, copy elision and dependency analysis.
Chances are, the code generated ends up being exactly the same.
Of course, adding the
cout
statements completely wrecks that. Compare generated code without the constructor/destructor side-effects spoiling it!
Fair comparison using no-side-effects generates identical code for the STL and Boost variants: http://paste.ubuntu.com/14544891/
The way STL (and boost algorithms) was designed, you can pass the functor by reference explicitly, if you want. You use std::ref
for this:
#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";
}
Prints
STL transform:
Ftor : init
~Ftor : ...
-----
Boost transform:
Ftor : init
~Ftor : ...
-----
NOTE Regardless of everything it's very very ironic to use a range algorithm on a container and construct a range boost::make_iterator_range(arg.begin(), arg.end())
instead of just using arg
:
boost::transform(arg, val.begin(), Ftor(x));