为了更好地说明我的问题,我编写了以下示例。
在下面的代码中,我介绍了一个函数对象(即
funObj
)。在
funObj
类的定义id
中,定义了一个完整的成员变量来保存每个成员的 IDfunObj
构造的 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;
}
输出:
打电话
func
...通过默认构造函数构造,对象 foo 具有 ID(1)
销毁 ID(1) 的对象 foo
打电话
for_each
...通过默认构造函数构造,对象 foo 具有 ID(2)
通过复制构造函数构造,对象 foo 具有 ID(3)
销毁 ID(2) 的对象 foo
销毁 ID(3) 的对象 foo
打电话
generate
...通过默认构造函数构造,对象 foo 具有 ID(4)
通过复制构造函数构造,对象 foo 具有 ID(5)
销毁 ID(5) 的对象 foo
已销毁 ID(4) 的对象 foo
使用
std::ref
...通过默认构造函数构造,对象 foo 具有 ID(6)
for_each
与ref
...通话
generate
与ref
...通话销毁 ID(6) 的对象 foo
讨论:
正如您从上面的输出中看到的那样,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
类中添加了一个移动构造函数(参见下面的代码)。
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 中开启了全面优化,并在发布模式下编译。
输出(VC++2013):
打电话
func
...通过默认构造函数构造,对象 foo 具有 ID(1)
销毁 ID(1) 的对象 foo
打电话
for_each
...通过默认构造函数构造,对象 foo 具有 ID(2)
通过移动构造函数构造,对象 foo 具有 ID(2)
销毁 ID(2) 的对象 foo
已销毁 ID(0) 的对象 foo
打电话
generate
...通过默认构造函数构造,对象 foo 具有 ID(3)
通过复制构造函数构造,对象 foo 具有 ID(4)
已销毁 ID(4) 的对象 foo
销毁 ID(3) 的对象 foo
使用
std::ref
...通过默认构造函数构造,对象 foo 具有 ID(5)
for_each
与ref
...通话
generate
与ref
...通话销毁 ID(5) 的对象 foo
输出 GCC 4.8
打电话
func
...通过默认构造函数构造,对象 foo 具有 ID(1)
销毁 ID(1) 的对象 foo
打电话
for_each
...通过默认构造函数构造,对象 foo 具有 ID(2)
通过移动构造函数构造,对象 foo 具有 ID(2)
销毁 ID(2) 的对象 foo
已销毁 ID(0) 的对象 foo
打电话
generate
...通过默认构造函数构造,对象 foo 具有 ID(3)
销毁 ID(3) 的对象 foo
通过默认构造函数构造,对象 foo 具有 ID(4)
for_each
与ref
...通话
generate
与ref
...通话已销毁 ID(4) 的对象 foo
std::generate
如果优化标志打开并且编译处于发布模式,并且除了定义了移动构造函数的事实,VC++2013 似乎会生成额外的副本。