转发与不转发传递给包装器的函数
Forwarding vs not Forwarding a function passed to a wrapper
我的问题是完美转发传递给我们包装器的函数有什么好处。
template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
{
std::cout<<"timing void function"<<std::endl;
//std::forward<T>(func)(std::forward<U>(args)...); //this vs next one
func(std::forward<U>(args)...);
std::cout<<"timing over"<<std::endl;
}
在参数转发的情况下,很明显转发保留了参数的左值与右值。不过调用前转发func有没有用?
假设我将临时仿函数和普通函数都传递给 time_function 包装器。
假设我有一个有状态的赋值器仿函数。分配器将被多次重用,因此每次都必须复制值。但是,如果分配器是右值,它可以只移动值:
struct Assignor {
std::string value;
void operator()(std::string& dest) const &
{
dest = value;
}
void operator()(std::string& dest) &&
{
dest = std::move(value);
}
};
现在,完美转发对右值产生影响:
Assignor f{std::string(10000, 'X')};
std::string a, b, c;
time_function(f, a); // copies the string
time_function(std::move(f), b); // should move the string
// but copies if you don't std::forward
time_function(Assignor{std::string(10000, 'Y')}, c); // same
(这只是一个如何优化函子的例子w.r.t。值类别。我知道它看起来有点做作,但人们总是会想出有创意的想法。)
顺便说一句,你应该使用 std::invoke
而不是直接调用 ()
:
std::invoke(std::forward<T>(func), std::forward<U>(args)...);
除了 L.F. 的回答之外,我想对一个潜在问题做一个小说明,这个问题很明显但仍然可以被忽略:有时它通过通用引用传递功能对象并将它们作为右值调用是危险的。假设不是
void time_function(T&& func, ...)
调用 std::forward<T>(func)(...)
一次,你有
void for_each(T&& func, ...)
可能会多次调用 std::forward<T>(func)(...)
。然后在第一次调用之后,所有进一步的调用都不安全。在 L.F. 的示例中,这对应于 "moved-from" 状态的多个分配:在第一次分配后, std::string value
成员将被保留在 "valid but unspecified state" 中,以后的分配将不会执行预期的操作(尽管它们不会是 UB)。如果 Assignor
没有 operator()
的 &&
重载,问题就不会出现。
我的问题是完美转发传递给我们包装器的函数有什么好处。
template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
{
std::cout<<"timing void function"<<std::endl;
//std::forward<T>(func)(std::forward<U>(args)...); //this vs next one
func(std::forward<U>(args)...);
std::cout<<"timing over"<<std::endl;
}
在参数转发的情况下,很明显转发保留了参数的左值与右值。不过调用前转发func有没有用?
假设我将临时仿函数和普通函数都传递给 time_function 包装器。
假设我有一个有状态的赋值器仿函数。分配器将被多次重用,因此每次都必须复制值。但是,如果分配器是右值,它可以只移动值:
struct Assignor {
std::string value;
void operator()(std::string& dest) const &
{
dest = value;
}
void operator()(std::string& dest) &&
{
dest = std::move(value);
}
};
现在,完美转发对右值产生影响:
Assignor f{std::string(10000, 'X')};
std::string a, b, c;
time_function(f, a); // copies the string
time_function(std::move(f), b); // should move the string
// but copies if you don't std::forward
time_function(Assignor{std::string(10000, 'Y')}, c); // same
(这只是一个如何优化函子的例子w.r.t。值类别。我知道它看起来有点做作,但人们总是会想出有创意的想法。)
顺便说一句,你应该使用 std::invoke
而不是直接调用 ()
:
std::invoke(std::forward<T>(func), std::forward<U>(args)...);
除了 L.F. 的回答之外,我想对一个潜在问题做一个小说明,这个问题很明显但仍然可以被忽略:有时它通过通用引用传递功能对象并将它们作为右值调用是危险的。假设不是
void time_function(T&& func, ...)
调用 std::forward<T>(func)(...)
一次,你有
void for_each(T&& func, ...)
可能会多次调用 std::forward<T>(func)(...)
。然后在第一次调用之后,所有进一步的调用都不安全。在 L.F. 的示例中,这对应于 "moved-from" 状态的多个分配:在第一次分配后, std::string value
成员将被保留在 "valid but unspecified state" 中,以后的分配将不会执行预期的操作(尽管它们不会是 UB)。如果 Assignor
没有 operator()
的 &&
重载,问题就不会出现。