【发布时间】:2014-06-30 02:00:35
【问题描述】:
为了更好地说明我的问题,我编写了以下示例。
在下面的代码中,我引入了function object(即
funObj)。在
funObj类的定义中,定义了一个名为id的整数成员变量来保存每个构造的funObj的ID 和一个静态整数成员变量n,以计算创建的funObj对象。因此,每次构造对象
funObj时,n都会增加一,并将其值分配给新创建的funObj的id字段。此外,我还定义了一个默认构造函数、一个复制构造函数和一个析构函数。这三个人都在向
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)
用
ref呼叫for_each...用
ref调用generate...ID(6) 的销毁对象 foo
讨论:
从上面的输出中可以看出,使用funObj 类型的临时对象调用函数func 会导致构造单个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)
用
ref呼叫for_each...用
ref呼叫generate...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)
用
ref呼叫for_each...用
ref呼叫generate...ID(4) 的销毁对象 foo
似乎 VC++2013 std::generate 如果优化标志打开并且编译处于发布模式并且除了定义了移动构造函数这一事实之外,还会生成额外的副本。
【问题讨论】:
-
总结大部分内容,我想您只想问“为什么 STL 采用值谓词?”
-
通过定义复制构造函数,您可以防止编译器生成隐式定义的移动构造函数。您可以通过定义自己的移动构造函数来阻止大部分副本。
-
FWIW VS2013 无论如何都不会生成移动构造函数。你总是需要提供你自己的。玩得开心。
-
@40two - 您需要在打开优化的情况下进行编译。否则,您将运行“已检查但速度较慢”的调试版本。来自调试版本的统计数据并不是编译器真正可以生成的代码的真实画面。
-
如果你为你的函数对象类定义一个移动构造函数会发生什么?如果 STL 实现者正确地完成了他们的工作,那应该会消除 STL 算法中的额外副本。
标签: c++ visual-c++ c++11 stl g++4.8