I've coded the following example in order to better illustrate my questions.
In the code below, I introduce a function object (i.e.,
funObj
).In
funObj
class's definition an integral member variable calledid
is defined to hold the ID of everyfunObj
constructed and a static integral member variablen
to count thefunObj
objects created.Thus, every time an object
funObj
is constructedn
is increased by one and its value is assigned to theid
field of the newly createdfunObj
.Furthermore, I've defined a default constructor, a copy constructor and a destructor. All three are printing messages to the
stdout
in order to signify their invocation along with the ID of thefunObj
they are referring to.I've also defined a function
func
that takes as inputs by value objects of typefunObj
.
Code:
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
template<typename T>
class funObj {
std::size_t id;
static std::size_t n;
public:
funObj() : id(++n)
{
std::cout << " Constructed via the default constructor, object foo with ID(" << id << ")" << std::endl;
}
funObj(funObj const &other) : id(++n)
{
std::cout << " Constructed via the copy constructor, object foo with ID(" << id << ")" << std::endl;
}
~funObj()
{
std::cout << " Destroyed object foo with ID(" << id << ")" << std::endl;
}
void operator()(T &elem)
{
}
T operator()()
{
return 1;
}
};
template<typename T>
void func(funObj<T> obj) { obj(); }
template<typename T>
std::size_t funObj<T>::n = 0;
int main()
{
std::vector<int> v{ 1, 2, 3, 4, 5, };
std::cout << "> Calling `func`..." << std::endl;
func(funObj<int>());
std::cout << "> Calling `for_each`..." << std::endl;
std::for_each(std::begin(v), std::end(v), funObj<int>());
std::cout << "> Calling `generate`..." << std::endl;
std::generate(std::begin(v), std::end(v), funObj<int>());
// std::ref
std::cout << "> Using `std::ref`..." << std::endl;
auto fobj1 = funObj<int>();
std::cout << "> Calling `for_each` with `ref`..." << std::endl;
std::for_each(std::begin(v), std::end(v), std::ref(fobj1));
std::cout << "> Calling `generate` with `ref`..." << std::endl;
std::for_each(std::begin(v), std::end(v), std::ref(fobj1));
return 0;
}
Output:
Calling
func
...Constructed via the default constructor, object foo with ID(1)
Destroyed object foo with ID(1)
Calling
for_each
...Constructed via the default constructor, object foo with ID(2)
Constructed via the copy constructor, object foo with ID(3)
Destroyed object foo with ID(2)
Destroyed object foo with ID(3)
Calling
generate
...Constructed via the default constructor, object foo with ID(4)
Constructed via the copy constructor, object foo with ID(5)
Destroyed object foo with ID(5)
Destroyed object foo with ID(4)
Using
std::ref
...Constructed via the default constructor, object foo with ID(6)
Calling
for_each
withref
...Calling
generate
withref
...Destroyed object foo with ID(6)
Discussion:
As you can see from the output above, calling function func
with a temporary object of type funObj
results in the construction of a single funObj
object (even though func
passes its argument by value). However, this seems not to be the case when passing temporary objects of type funObj
to STL algorithms std::for_each
and std::generate
. In the former cases the copy constructor is evoked and an extra funObj
is constructed. In quite a few applications the creation of such "unnecessary" copies deteriorates the performance of the algorithm significantly. Based on this fact the following questions are being raised.
Questions:
- I know that most STL algorithms pass their argument by value. However, compared to
func
, that also passes its input argument by value, the STL algorithms generate an extra copy. What's the reason for this "unnecessary" copy? - Is there a way to eliminate such "unnecessary" copies?
- When calling
std::for_each(std::begin(v), std::end(v), funObj<int>())
andfunc(funObj<int>())
in which scope does temporary objectfunObj<int>
lives, for each case respectively? - I've tried to use
std::ref
in order to force pass by reference and as you can see the "unnecessary" copy was eliminated. However, when I try to pass a temporary object tostd::ref
(i.e.,std::ref(funObj<int>())
) I get a compiler error. Why such kind of statements are illegal? - The output was generated using VC++2013. As you can see there's an anomaly when calling
std::for_each
the destructors of the objects are being called in reversed order. Why is that so? - When I run the code on Coliru that runs GCC v4.8 the anomaly with destructors is fixed however
std::generate
doesn't generate an extra copy. Why is that so?
Details/Comments:
- The output above was generated from VC++2013.
Update:
- I've also added to
funObj
class a move constructor (see code below).
funObj(funObj&& other) : id(other.id)
{
other.id = 0;
std::cout << " Constructed via the move constructor, object foo with ID(" << id << ")" << std::endl;
}
- I've also turned on full optimization in VC++2013 and compiled in release mode.
Output (VC++2013):
Calling
func
...Constructed via the default constructor, object foo with ID(1)
Destroyed object foo with ID(1)
Calling
for_each
...Constructed via the default constructor, object foo with ID(2)
Constructed via the move constructor, object foo with ID(2)
Destroyed object foo with ID(2)
Destroyed object foo with ID(0)
Calling
generate
...Constructed via the default constructor, object foo with ID(3)
Constructed via the copy constructor, object foo with ID(4)
Destroyed object foo with ID(4)
Destroyed object foo with ID(3)
Using
std::ref
...Constructed via the default constructor, object foo with ID(5)
Calling
for_each
withref
...Calling
generate
withref
...Destroyed object foo with ID(5)
Output GCC 4.8
Calling
func
...Constructed via the default constructor, object foo with ID(1)
Destroyed object foo with ID(1)
Calling
for_each
...Constructed via the default constructor, object foo with ID(2)
Constructed via the move constructor, object foo with ID(2)
Destroyed object foo with ID(2)
Destroyed object foo with ID(0)
Calling
generate
...Constructed via the default constructor, object foo with ID(3)
Destroyed object foo with ID(3)
Constructed via the default constructor, object foo with ID(4)
Calling
for_each
withref
...Calling
generate
withref
...Destroyed object foo with ID(4)
It seems that VC++2013 std::generate
generates an extra copy no-matter if optimization flags are on and compilation is in release mode and besides the fact that a move constructor is defined.
STL algorithms return the function object. This happens so that the mutation on the object will be observable. Your
func
returns void so that's a copy less.generate
does not return a thing (see comment by dyp)Well unnecessary is a bit too strong. The whole point of functors is to be lightweight objects so that a copy wouldn't matter. As for a way, the one you provide (std::ref) will do the job, alas a copy of the
std::ref
will be generated (your object won't get copied though)Another way would be to qualify the call of the algorithm
then the function object type will be a reference :
The body of
std_for_each
is expanded as follows :your function reads
The comments
1
and2
mark the lifespan in each case. Note though that if a return value optimization applies (named or unnamed), then the compiler may generate code that places the return value (the function object in for_each) in the stack frame of the caller, so the life span is longer.std::ref
does not work with r-value references (STL code follows):you need to pass an l-value
Check the settings for each compilation. With optimizations ON (and in Release for VS) copy elision / elimination of extra copies / ignoring non observable behaviors, are possible.
Secondly (as far as I can see) in VS 2013 the functor in
for_each
and the generator ingenerate
are both passed by value (there's no signature accepting an r-value reference) so it would be clearly a matter of copy elision to save the extra copy.For what matters, the STL implementation in gcc also has no signatures that accept r-value references (please notify me if one having is spotted)
so I may be going out on limb on this one and assume, that defining move semantics for your functor has no effect and only compiler optimizations apply to eliminate copies
The move semantics introduced in C++11 exist to largely alleviate this set of 'unnecessary' copies. If you define a
move constructor
for your function-objects the STL willmove
the function-object (even/especially if it is a temporary) which will prevent the copy from occurring. This will allow you to use the STL algorithms with value-semantics without sacrificing too much in the way of performance. It will also allow you to use temporary function-objects as desired.