【发布时间】:2014-12-20 00:22:22
【问题描述】:
首先,考虑以下代码:
#include <iostream>
#include <functional>
struct Noisy
{
Noisy() { std::cout << "Noisy()" << std::endl; }
Noisy(const Noisy&) { std::cout << "Noisy(const Noisy&)" << std::endl; }
Noisy(Noisy&&) { std::cout << "Noisy(Noisy&&)" << std::endl; }
~Noisy() { std::cout << "~Noisy()" << std::endl; }
};
void foo(Noisy n)
{
std::cout << "foo(Noisy)" << std::endl;
}
int main()
{
Noisy n;
std::function<void(Noisy)> f = foo;
f(n);
}
及其在不同编译器中的输出:
Visual C++ (see live)
Noisy()
Noisy(const Noisy&)
Noisy(Noisy&&)
foo(Noisy)
~Noisy()
~Noisy()
~Noisy()
Clang (libc++) (see live)
Noisy()
Noisy(const Noisy&)
Noisy(Noisy&&)
foo(Noisy)
~Noisy()
~Noisy()
~Noisy()
GCC 4.9.0 (see live)
Noisy()
Noisy(const Noisy&)
Noisy(Noisy&&)
Noisy(Noisy&&)
foo(Noisy)
~Noisy()
~Noisy()
~Noisy()
~Noisy()
也就是说,与 Visual C++(和 Clang+libc++)相比,GCC 执行一个移动/复制操作,让我们同意,并不是在所有情况下都有效(例如 std::array<double, 1000> 参数)。
据我了解,std::function 需要对一些包含实际函数对象的内部包装器进行虚拟调用(在我的情况下为 foo)。因此,使用转发引用和完美转发是不可能的(因为虚拟成员函数不能被模板化)。
但是,我可以想象实现可以在内部 std::forward 内部所有参数,无论它们是通过值传递还是通过引用传递,如下所示:
// interface for callable objects with given signature
template <class Ret, class... Args>
struct function_impl<Ret(Args...)> {
virtual Ret call(Args&&... args) = 0; // rvalues or collaped lvalues
};
// clever function container
template <class Ret, class... Args>
struct function<Ret(Args...)> {
// ...
Ret operator()(Args... args) { // by value, like in the signature
return impl->call(std::forward<Args>(args)...); // but forward them, why not?
}
function_impl<Ret(Args...)>* impl;
};
// wrapper for raw function pointers
template <class Ret, class... Args>
struct function_wrapper<Ret(Args...)> : function_impl<Ret(Args...)> {
// ...
Ret (*f)(Args...);
virtual Ret call(Args&&... args) override { // see && next to Args!
return f(std::forward<Args>(args)...);
}
};
因为按值传递的参数只会变成右值引用(很好,为什么不呢?),右值引用将折叠并保留为右值引用,而左值引用将折叠并保留为左值引用(see this proposal live)。这避免了任意数量的内部助手/委托之间的复制/移动。
所以我的问题是,为什么 GCC 对按值传递的参数执行额外的复制/移动操作,而 Visual C++(或 Clang+libc++)却没有(因为它似乎没有必要)?我希望 STL 的设计/实现能够获得最佳性能。
请注意,在std::function 签名中使用右值引用,如std::function<void(Noisy&&)>,对我来说不是解决方案。
请注意,我并不是在寻求解决方法。我认为这两种可能的解决方法都不正确。
a) 使用 const 左值引用!
为什么不呢?因为现在当我使用右值调用 f 时:
std::function<void(const Noisy&)> f = foo;
f(Noisy{});
它暂时禁止Noisy 的移动操作并强制复制。
b) 然后使用非常量右值引用!
为什么不呢?因为现在当我使用左值调用 f 时:
Noisy n;
std::function<void(Noisy&&)> f = foo;
f(n);
它根本不编译。
【问题讨论】:
-
您的问题是:“为什么 GCC 对按值传递的参数执行额外的复制/移动操作?”,在那里得到了回答。还是我错过了什么?
-
@BЈовић 看起来另一个答案解释了为什么有 2 个副本,而这个答案问为什么 gcc 有 3 个。
-
我相信在 gcc 的 bugzilla 中提交增强 PR 是可行的方法。他们可能选择不改变它。 operator() 不直接调用该函数,而是将其委托给一个助手
_M_invoker,因此是额外的副本。 -
我认为原因可能是 GCC 的
std::function是基于我们的std::tr1::function,它是在右值引用存在多年之前编写的。我已经优化了其他呼叫包装器,例如std::bind、std::thread和std::async以使用完美转发,但function可能仍然不是最理想的。我去看看。
标签: c++ c++11 gcc libstdc++ std-function