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
funObjclass's definition an integral member variable calledidis defined to hold the ID of everyfunObjconstructed and a static integral member variablento count thefunObjobjects created.Thus, every time an object
funObjis constructednis increased by one and its value is assigned to theidfield of the newly createdfunObj.Furthermore, I've defined a default constructor, a copy constructor and a destructor. All three are printing messages to the
stdoutin order to signify their invocation along with the ID of thefunObjthey are referring to.I've also defined a function
functhat 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_eachwithref...Calling
generatewithref...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::refin 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_eachthe 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::generatedoesn'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
funObjclass 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_eachwithref...Calling
generatewithref...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_eachwithref...Calling
generatewithref...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.