funObj
)。 funObj
类的定义中,定义了一个称为id
的积分成员变量以保存所构造的每个funObj
的ID,并定义了一个静态积分成员变量n
来计数创建的funObj
对象。 funObj
时,n
都会增加1,并将其值分配给新创建的id
的funObj
字段。 stdout
上,以表示它们的调用以及它们所引用的funObj
的ID。 func
函数,该函数将funObj
类型的值对象作为输入。 代码:
#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;
}
输出:
Calling
func
...Constructed via the default constructor, object foo with ID(1)
Destroyed object foo with ID(1)
调用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)
调用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)
使用std::ref
...
Constructed via the default constructor, object foo with ID(6)
用for_each
调用ref
...
用generate
调用ref
...
Destroyed object foo with ID(6)
讨论:
从上面的输出中可以看到,使用func
类型的临时对象调用函数funObj
会导致构造单个funObj
对象(即使func
通过值传递其参数)。但是,当将funObj
类型的临时对象传递给STL算法std::for_each
和std::generate
时,情况似乎并非如此。在前一种情况下,将引发复制构造函数并构造一个额外的funObj
。在许多应用中,创建这种“不必要的”拷贝会大大降低算法的性能。基于这一事实,提出了以下问题。
问题:我知道大多数STL算法都按值传递其参数。但是,与 func
(也按值传递其输入参数)相比,STL算法会生成一个额外的拷贝。此“不必要”拷贝的原因是什么?是否有消除这种“不必要”拷贝的方法? 在每种情况下分别调用 std::for_each(std::begin(v), std::end(v), funObj<int>())
和func(funObj<int>())
时,临时对象funObj<int>
驻留在哪个范围内?我尝试使用 std::ref
来强制通过引用传递,并且如您所见,消除了“不必要的”拷贝。但是,当我尝试将临时对象传递给std::ref
(即std::ref(funObj<int>())
)时,出现编译器错误。为什么这种陈述是非法的?使用VC++ 2013生成了输出。如您所见,调用 std::for_each
时存在异常,以相反的顺序调用对象的析构函数。为什么会这样?当我在运行GCC v4.8的Coliru上运行代码时,析构函数异常已修复,但是 std::generate
不会生成额外的拷贝。为什么会这样?
详细信息/评论:上面的输出是从VC++ 2013生成的。
更新:我还向 funObj
类添加了一个move构造函数(请参见下面的代码)。funObj(funObj&& other) : id(other.id) { other.id = 0; std::cout << " Constructed via the move constructor, object foo with ID(" << id << ")" << std::endl; }
我还已在VC++ 2013中启用了完全优化并在 Release模式下进行了编译。
输出(VC++ 2013):
Calling
func
...Constructed via the default constructor, object foo with ID(1)
Destroyed object foo with ID(1)
调用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)
调用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)
使用std::ref
...
Constructed via the default constructor, object foo with ID(5)
用for_each
调用ref
...
用generate
调用ref
...
Destroyed object foo with ID(5)
输出GCC 4.8
Calling
func
...Constructed via the default constructor, object foo with ID(1)
Destroyed object foo with ID(1)
调用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)
调用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)
用for_each
调用ref
...
用generate
调用ref
...
Destroyed object foo with ID(4)
似乎VC++ 2013std::generate
如果启用了优化标志且编译处于 Release模式,并且定义了move构造函数,则将生成一个额外的拷贝。
最佳答案
1 - 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?
STL算法返回函数对象。发生这种情况,以便可以观察到对象上的突变。您的
func
返回void,因此少了一个拷贝。generate
不返回任何东西(请参阅dyp)的评论2 - Is there a way to eliminate such "unnecessary" copies?
那么不必要的有点太强大了。仿函数的重点是轻量级对象,因此复制无关紧要。至于一种方式,您提供的(std::ref)将完成此工作,可惜会生成
std::ref
的拷贝(不过您的对象将不会被复制)另一种方法是让限定算法的调用
那么该函数对象类型将是一个引用:
auto fobj1 = funObj<int>();
std::for_each<std::vector<int>::iterator, std::vector<int>::iterator,
funObj<int>&> // this is where the magic happens !!
(std::begin(v), std::end(v), fobj1);
3 - When calling std::for_each(std::begin(v), std::end(v), funObj()) and func(funObj()) in which scope does temporary object funObj lives, for each case respectively?
std_for_each
的主体扩展如下:template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function fn)
{ // 1
while (first!=last) {
fn (*first);
++first;
}
return fn; // or, since C++11: return move(fn);
// 2
}
你的功能读
template<typename T>
void func(funObj<T> obj)
{ // 1.
obj();
// 2.
}
每种情况下,注释
1
和2
标记生命周期。请注意,虽然如果返回值优化应用了(已命名或未命名),则编译器可能会生成将返回值(函数对象位于for_each中)放置在调用方的堆栈框架中的代码,因此使用生命周期更长。4 - 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 to std::ref (i.e., std::ref(funObj())) I get a compiler error. Why such kind of statements are illegal?
std::ref
不适用于r值引用(后接STL代码):template<class _Ty>
void ref(const _Ty&&) = delete;
您需要传递一个L值
5 - 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?
6 - 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?
for_each
中的仿函数和generate
中的生成器均按值传递(,没有签名接受r值引用),因此显然,这是复制的问题elision 保存多余的拷贝。 重要的是,STL implementation in gcc也没有接受r值引用的签名(如果发现有r值引用,请通知我)
template<typename _InputIterator, typename _Function>
_Function
for_each(_InputIterator __first, _InputIterator __last, _Function __f)
{
// concept requirements
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
__glibcxx_requires_valid_range(__first, __last);
for (; __first != __last; ++__first)
__f(*__first);
return _GLIBCXX_MOVE(__f);
}
因此,我可能会对此一无所知,并假设为您的仿函数定义移动语义没有任何效果,只有编译器优化可用于消除拷贝
关于c++ - 调用C++/STL算法时消除不必要的拷贝,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23613574/