为了更好地说明我的问题,我编写了以下示例。
在下面的代码中,我介绍了function object(即funObj
)。
在funObj
类的定义中,定义了一个名为id
的整数成员变量,用于保存构造的每个funObj
的ID和一个静态整数成员变量{{ 1}}计算创建的n
个对象。
因此,每次构建对象funObj
时,funObj
都会增加1,其值将分配给新创建的n
id
字段。
此外,我已经定义了默认构造函数,复制构造函数和析构函数。这三个人都在向funObj
打印消息,以便表明他们的调用以及他们所指的stdout
的ID。
我还定义了一个函数funObj
,它被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; }
...通过默认构造函数构造,对象foo为ID(1)
使用ID(1)
销毁对象foo致电
func
...通过默认构造函数构造,对象foo为ID(2)
通过复制构造函数构造,对象foo为ID(3)
使用ID(2)
销毁对象foo使用ID(3)
销毁对象foo致电
for_each
...通过默认构造函数构造,对象foo为ID(4)
通过复制构造函数构造,ID为对象foo(5)
使用ID(5)
销毁对象foo使用ID(4)
销毁对象foo使用
generate
...通过默认构造函数构造,对象foo为ID(6)
使用
致电std::ref
...for_each
使用
致电ref
...generate
使用ID(6)
销毁对象foo
从上面的输出中可以看出,使用类型为ref
的临时对象调用函数func
会导致构造单个funObj
对象(即使funObj
}通过值传递其参数)。但是,将func
类型的临时对象传递给STL算法funObj
和std::for_each
时似乎并非如此。在前一种情况下,引发了复制构造函数,并构造了一个额外的std::generate
。在很多应用程序中创建了这样的&#34;不必要的&#34;副本显着降低了算法的性能。基于这一事实,正在提出以下问题。
funObj
相比,它还通过值传递其输入参数,STL算法生成一个额外的副本。这是什么原因以及不必要的&#34;副本?func
和std::for_each(std::begin(v), std::end(v), funObj<int>())
范围内的临时对象func(funObj<int>())
?funObj<int>
强制通过引用传递,因为您可以看到&#34;不必要的&#34;副本被删除了。但是,当我尝试将临时对象传递给std::ref
(即std::ref
)时,我收到编译器错误。为什么这种陈述是非法的?std::ref(funObj<int>())
时,异常会以相反的顺序调用对象的析构函数。为什么会这样?std::for_each
没有生成额外的副本。为什么会这样?std::generate
类中添加了一个移动构造函数(请参阅下面的代码)。funObj
致电
funObj(funObj&& other) : id(other.id) { other.id = 0; std::cout << " Constructed via the move constructor, object foo with ID(" << id << ")" << std::endl; }
...通过默认构造函数构造,对象foo为ID(1)
使用ID(1)
销毁对象foo致电
func
...通过默认构造函数构造,对象foo为ID(2)
通过移动构造函数构造,具有ID(2)的对象foo
使用ID(2)
销毁对象foo使用ID(0)
销毁对象foo致电
for_each
...通过默认构造函数构造,对象foo为ID(3)
通过复制构造函数构造,具有ID(4)的对象foo
使用ID(4)
销毁对象foo使用ID(3)
销毁对象foo使用
generate
...通过默认构造函数构造,对象foo为ID(5)
使用
致电std::ref
...for_each
使用
致电ref
...generate
使用ID(5)
销毁对象foo
致电
ref
...通过默认构造函数构造,对象foo为ID(1)
使用ID(1)
销毁对象foo致电
func
...通过默认构造函数构造,对象foo为ID(2)
通过移动构造函数构造,具有ID(2)的对象foo
使用ID(2)
销毁对象foo使用ID(0)
销毁对象foo致电
for_each
...通过默认构造函数构造,对象foo为ID(3)
使用ID(3)
销毁对象foo通过默认构造函数构造,对象foo为ID(4)
使用
致电generate
...for_each
使用
致电ref
...generate
使用ID(4)
销毁对象foo
如果启用了优化标志并且编译处于发布模式,并且除了定义了移动构造函数之外,VC ++ 2013 ref
似乎生成了一个额外的副本。
答案 0 :(得分:4)
1 - 我知道大多数STL算法都是按值传递参数。但是,与func相比,它还通过值传递其输入参数,STL算法生成一个额外的副本。这个“不必要”的副本是什么原因?
STL算法返回函数对象。发生这种情况,以便可以观察到对象上的突变。您的func
返回无效,因此副本较少。
generate
不会返回任何内容(请参阅dyp)的评论2 - 有没有办法消除这种“不必要的”副本?
不必要有点太强了。仿函数的重点是轻量级对象,因此副本无关紧要。至于一种方式,你提供的那个(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 - 调用std :: for_each(std :: begin(v),std :: end(v),funObj())和func(funObj()),其中范围是临时对象funObj生活,分别针对每个案例?
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 - 我试图使用std :: ref来强制传递引用,因为你可以看到“不必要的”副本被删除了。但是,当我尝试将临时对象传递给std :: ref(即std :: ref(funObj()))时,我收到编译器错误。为什么这种陈述是非法的?
std::ref
不适用于r值引用(后面是STL代码):
template<class _Ty>
void ref(const _Ty&&) = delete;
你需要传递一个l值
5 - 使用VC ++ 2013生成输出。正如您所看到的,在调用std :: for_each时会出现异常,对象的析构函数将以相反的顺序调用。为什么会这样?
6 - 当我在运行GCC v4.8的Coliru上运行代码时,析构函数的异常是固定的,但是std :: generate不会生成额外的副本。为什么会这样?
检查每个编辑的设置。通过优化ON(以及在Release for VS中)复制省略/消除额外副本/忽略不可观察的行为是可能的。
其次(据我所知)在VS 2013中,for_each
中的仿函数和generate
中的生成器都按值传递(没有签名接受r - 值参考)因此显然需要复制省略来保存额外的副本。
重要的是,STL implementation in gcc也没有接受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);
}
因此我可能会对此问题采取行动并假设,为您的仿函数定义移动语义没有任何效果,只有编译器优化适用于消除副本
答案 1 :(得分:3)
C ++ 11中引入的移动语义存在很大程度上缓解了这组“不必要”的副本。如果为函数对象定义move constructor
,STL将move
函数对象(偶数/特别是如果它是临时的)将阻止副本发生。这将允许您使用具有值语义的STL算法,而不会牺牲太多的性能。它还允许您根据需要使用临时函数对象。