在调用C ++ / STL算法时消除不必要的副本 [英] eliminate unnecessary copies when calling C++/STL algorithms
问题描述
-
我编写了以下示例,以更好地说明我的问题。
-
在下面的代码中,我介绍了函数对象(即
funObj
)。 -
在
funObj
类的定义是一个整数成员变量id
定义为保存构造的每个funObj
的ID以及静态整数成员变量n
funObj
创建的对象。 -
因此,每次构建一个对象
funObj
n
增加一并且其值被分配给新创建的funObj
的id
字段。 -
此外,我定义了一个默认构造函数,一个复制构造函数和一个析构函数。所有这三个都在
stdout
上打印信息以表示它们的调用以及它们所引用的funObj
的ID至。 -
我还定义了一个函数
func
c $ c> funObj 。
/ h2>
#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< 通过默认构造函数构造,具有ID(<< id<<)< std :: endl;
}
funObj(funObj const& other):id(++ n)
{
std :: cout< 通过拷贝构造函数构造,具有ID(<< id<<)< std :: endl;
}
〜funObj()
{
std :: cout< Destroyed object foo with ID(<< id<<)< std :: endl;
}
void operator()(T& elem)
{
}
$ b 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<< >调用`func` ...< std :: endl;
func(funObj< int>());
std :: cout<< >调用`for_each` ...< std :: endl;
std :: for_each(std :: begin(v),std :: end(v),funObj< int>());
std :: cout<< >调用'generate` ...< std :: endl;
std :: generate(std :: begin(v),std :: end(v),funObj< int>());
// std :: ref
std :: cout<< >使用`std :: ref` ...<< std :: endl;
auto fobj1 = funObj< int>();
std :: cout<< >用`ref`调用`for_each` ...< std :: endl;
std :: for_each(std :: begin(v),std :: end(v),std :: ref(fobj1));
std :: cout<< >用`ref`调用`generate` ...< std :: endl;
std :: for_each(std :: begin(v),std :: end(v),std :: ref(fobj1));
return 0;
}
输出:
调用 func
...
通过默认构造函数,ID为1的对象foo构造
使用ID(1)销毁对象foo
调用 for_each
...
通过默认构造函数构造,具有ID(2)的对象foo
通过拷贝构造函数构造,具有ID(3)的对象foo
使用ID(2)销毁对象foo
销毁ID为(3)的对象foo
调用生成
...
通过默认构造函数构造,具有ID(4)的对象foo
通过拷贝构造函数构造,ID为5的对象foo
使用ID(5)销毁对象foo
销毁ID为4的对象foo
使用 std :: ref
...
通过默认构造函数构造,ID为6的对象foo
调用 for_each 与 ref
...
调用 / code>与 ref
...
销毁对象foo with ID(6)
讨论:
$ b
从上面的输出可以看出,使用类型 funObj $的临时对象调用函数 func
c $ c>导致构造单个 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
?
- 我试图使用
std :: ref
,以强制传递通过引用,因为你可以看到不必要的副本被删除。然而,当我试图传递临时对象到 std :: ref
(即 std :: ref(funObj< int> / code>)我得到一个编译器错误。为什么此类语句是非法的?
- 输出使用VC ++ 2013生成。正如你可以看到,当调用
std :: for_each
时,有一个异常,正在以相反的顺序调用对象的析构函数。为什么会这样?
- 当我在 Coliru上运行代码时运行GCC v4.8,具有析构函数的异常是固定的,但
std :: generate
不会生成额外的副本。为什么会这样?
详细信息/注释:
- 上面的输出是从VC ++ 2013生成的。
#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< 通过默认构造函数构造,具有ID(<< id<<)< std :: endl;
}
funObj(funObj const& other):id(++ n)
{
std :: cout< 通过拷贝构造函数构造,具有ID(<< id<<)< std :: endl;
}
〜funObj()
{
std :: cout< Destroyed object foo with ID(<< id<<)< std :: endl;
}
void operator()(T& elem)
{
}
$ b 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<< >调用`func` ...< std :: endl;
func(funObj< int>());
std :: cout<< >调用`for_each` ...< std :: endl;
std :: for_each(std :: begin(v),std :: end(v),funObj< int>());
std :: cout<< >调用'generate` ...< std :: endl;
std :: generate(std :: begin(v),std :: end(v),funObj< int>());
// std :: ref
std :: cout<< >使用`std :: ref` ...<< std :: endl;
auto fobj1 = funObj< int>();
std :: cout<< >用`ref`调用`for_each` ...< std :: endl;
std :: for_each(std :: begin(v),std :: end(v),std :: ref(fobj1));
std :: cout<< >用`ref`调用`generate` ...< std :: endl;
std :: for_each(std :: begin(v),std :: end(v),std :: ref(fobj1));
return 0;
}
调用 func
...
通过默认构造函数,ID为1的对象foo构造
使用ID(1)销毁对象foo
调用 for_each
...
通过默认构造函数构造,具有ID(2)的对象foo
通过拷贝构造函数构造,具有ID(3)的对象foo
使用ID(2)销毁对象foo
销毁ID为(3)的对象foo
调用生成
...
通过默认构造函数构造,具有ID(4)的对象foo
通过拷贝构造函数构造,ID为5的对象foo
使用ID(5)销毁对象foo
销毁ID为4的对象foo
使用 std :: ref
...
通过默认构造函数构造,ID为6的对象foo
调用 for_each 与
ref
...
调用 / code>与
ref
...
销毁对象foo with ID(6)
$ b
funObj $的临时对象调用函数 func
c $ c>导致构造单个 funObj
对象(即使 func
通过其参数传递其值)。但是,当将类型 funObj
的临时对象传递给STL算法 std :: for_each
时,似乎并非如此 std :: generate
。在前一种情况下,复制构造函数被调用,并构造一个额外的 funObj
。在相当多的应用中,这种不必要的副本的创建显着地恶化了算法的性能。基于这个事实,提出了以下问题。
func
相比,它还将其输入参数按值传递,因此STL算法生成额外的副本。 std :: for_each(std :: begin(v),std :: end(v),funObj< int>())
和 func(funObj< int> ())
其中范围分别对每个案例分别生成临时对象 funObj
? std :: ref
,以强制传递通过引用,因为你可以看到不必要的副本被删除。然而,当我试图传递临时对象到 std :: ref
(即 std :: ref(funObj< int> / code>)我得到一个编译器错误。为什么此类语句是非法的?
std :: for_each
时,有一个异常,正在以相反的顺序调用对象的析构函数。为什么会这样? std :: generate
不会生成额外的副本。为什么会这样?- 我也添加了
funObj
class a move constructor (见下面的代码)。
funObj (funObj&&& other):id(other.id)
{
other.id = 0;
std :: cout<< 通过移动构造函数构造,具有ID(<< id<<)< std :: endl;
}
- 我还在VC ++ 2013中打开了完全优化并在发布模式下编译。
++ 2013):
调用
func
...
使用ID(1)的默认构造函数构造对象foo
销毁object foo with ID(1)
调用
for_each
...
通过默认构造函数构造,具有ID(2)的对象foo
移动构造函数,具有ID(2)的对象foo
销毁ID为(2)的对象foo
object foo with ID(0)
调用
生成
...
通过默认构造函数构造,具有ID(3)的对象foo
复制构造函数,带有ID(4)的对象foo
销毁ID为(4)的对象foo
object foo with ID(3)
使用
std :: ref
.. 。
通过默认构造函数构造ID为5的对象foo
调用
for_each
与ref
...
使用
调用ref
...生成
销毁ID为(5)的物件foo
输出GCC 4.8
调用
func
。
通过默认构造函数,ID为1的对象foo构造
使用ID(1)销毁对象foo
调用
for_each
...
通过默认构造函数构造,具有ID(2)的对象foo
通过移动构造函数构造,ID为2的对象foo
使用ID(2)销毁对象foo
用ID(0)销毁对象foo
调用
生成
...
使用ID(3)的默认构造函数构造对象foo
具有ID(3)的销毁对象foo
通过默认构造函数构造,具有ID(4)的对象foo
调用
for_each
与ref
...
使用
调用ref
...生成
销毁ID为(4)的物件foo
看起来如果优化标志开启并且编译处于释放模式,并且除了移动的事实,VC ++ 2013 std :: generate
生成额外的复制无关构造函数。
1 - 我知道大多数STL算法传递他们的参数按值。然而,与func相比,它也通过值传递其输入参数,STL算法生成额外的副本。
STL演算法会传回函式物件 。这发生,以致对象上的突变将是可观察的。您的 func
返回void,所以这是一个副本。
- 很好,
生成
不会返回任何东西由 dyp)
2 - 是否有办法消除此类不必要的副本?
井不必要有点太强了。函子的整个点是轻量级的对象,所以一个副本不重要。对于一种方式,你提供的(std :: ref)将做这项工作,唉,一个副本 std :: ref
将生成(你的对象赢了'
另一种方法是限定算法的调用
那么函数对象类型将是一个引用:
auto fobj1 = funObj< int>();
std :: for_each< std :: vector< int> :: iterator,std :: vector< int> :: iterator,
funObj< int>& //这是魔法发生的地方!
(std :: begin(v),std :: end(v),fobj1);
3 - 当调用std :: for_each(std: :对于每种情况,对于每种情况,分别生成临时对象funObj的范围:begin(v),std :: end(v),funObj())和func(funObj())。
<$ c的正文$ c> std_for_each 扩展如下:
template< class InputIterator,class函数>
函数for_each(InputIterator first,InputIterator last,function fn)
{// 1
while(first!= last){
fn(* first);
++ first;
}
return fn; //或者,因为C ++ 11:return move(fn);
// 2
}
您的函数读为
模板< typename T>
void func(funObj< T> obj)
{// 1.
obj();
// 2.
}
注释 1
和 2
标记每种情况下的寿命。请注意,虽然如果应用了返回值优化(命名或未命名),编译器可能会生成将返回值(for_each中的函数对象)放置在调用者的堆栈框架中的代码,寿命更长。
4 - 我试图使用std :: ref以强制传递引用,看到不必要的副本被删除。然而,当我尝试传递临时对象到std :: ref(即,std :: ref(funObj()))我得到一个编译器错误。为什么此类语句是非法的?
std :: ref
不能使用r值引用(STL代码如下):
template< class _Ty>
void ref(const _Ty&&)= delete;
您需要传递l值
< blockquote>
5 - 输出使用VC ++ 2013生成。正如你可以看到,有一个异常,当调用std :: for_each时,对象的析构函数以相反的顺序被调用。为什么会这样?
6 - 当我运行在运行GCC v4.8的Coliru代码时,析构函数的异常是固定的, :: generate不会生成额外的副本。为什么会这样?
-
检查每个编辑的设置。通过优化ON(以及VS的发布)复制删除/消除额外副本/忽略不可观察的行为是可能的。
-
其次(在我看来)在VS 2013中的函数
for_each
generate
都是通过值传递的(没有签名接受r值引用),因此这很明显是 copy
重要的是, gcc中的STL实现也没有接受r值引用的签名(请
模板< typename _InputIterator,typename _Function>
_Function
for_each(_InputIterator __first,_InputIterator __last,_Function __f)
{
//概念要求
__glibcxx_function_requires(_InputIteratorConcept< _InputIterator>)
__glibcxx_requires_valid_range __first,__last);
for(; __first!= __last; ++ __ first)
__f(* __ first);
return _GLIBCXX_MOVE(__ f);
}
所以我可能会走出这一边的肢体,你的函子的移动语义没有效果,只有编译器优化适用于消除副本
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.
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 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.
- Well, to be precise,
generate
does not return a thing (see comment by dyp)
2 - Is there a way to eliminate such "unnecessary" copies?
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 :
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?
The body of std_for_each
is expanded as follows :
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
}
your function reads
template<typename T>
void func(funObj<T> obj)
{ // 1.
obj();
// 2.
}
The comments 1
and 2
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.
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
does not work with r-value references (STL code follows):
template<class _Ty>
void ref(const _Ty&&) = delete;
you need to pass an l-value
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?
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)
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);
}
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
这篇关于在调用C ++ / STL算法时消除不必要的副本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!