c++中传递回调函数参数的最佳方式
Best way of passing callback function parameters in C++
在c++中传递回调函数参数的最佳方法是什么?
我想简单地使用模板,像这样:
template <typename Function>
void DoSomething(Function callback)
这是在std::sort
中用于比较函数对象的方法。
使用 &&
传递呢?例如:
template <typename Function>
void DoSomething(Function&& callback)
这两种方法的优缺点是什么?为什么STL使用前者,例如在std::sort
中?
template <typename Function>
void DoSomething(Function&& callback)
…使用转发引用通过引用传递,对于函数只使用回调的情况来说是IMHO优越的。因为函子对象虽然通常很小,但可以任意大。然后按值传递它会产生一些不必要的开销。
形式参数可以以T&
、T const&
或T&&
结尾,这取决于实际的参数。
另一方面,通过引用传递简单的函数指针涉及到额外的不必要的间接,这在原则上可能意味着一些轻微的开销。
如果对给定的编译器、系统和应用程序是否重要存在疑问,则度量。
关于
值得注意的是," 为什么STL使用前者[按值传递]例如在
std::sort
中?
std::sort
自c++ 98以来就存在了,远远早于c++ 11中引入的转发引用,因此它最初不可能具有该签名。可能只是没有足够的动机来改善这一点。毕竟,一个人通常不应该修复那些有效的东西。不过,c++ 17中引入了带有" execution policy "参数的额外重载。
由于这涉及到c++ 11可能发生的变化,因此通常的基本原理来源(即Bjarne Stroustrup的"c++的设计和演变")没有涵盖它。,我不知道有什么确凿的答案。
使用template<class F> void call( F )
样式,您仍然可以返回"按引用传递"。只需执行call( std::ref( your callback ) )
。std::ref
覆盖operator()
并将其转发给所包含的对象。
template<class F> void call( F&& )
样式,您可以这样写:
template<class T>
std::decay_t<T> copy( T&& t ) { return std::forward<T>(t); }
显式复制某些内容,并强制call
使用f
的本地副本:
call( copy(f) );
所以这两种样式在默认情况下的行为主要不同。
因为这是c++ 11(或更高版本):<functional>
和std::function
在这里是你最好的朋友。
#include <iostream>
#include <functional>
#include <string>
void DoSomething(std::function<void()> callback) {
callback();
}
void PrintSomething() {
std::cout << "Hello!" << std::endl;
}
int main()
{
DoSomething(PrintSomething);
DoSomething([]() { std::cout << "Hello again!" << std::endl; });
}
在我看来,使用&&
将是更好的方法,主要是因为它避免了在参数为左值时复制构造函子。对于lambda,这意味着避免复制构造所有捕获的值。在std::function
的情况下,您还需要额外的堆分配,除非使用了小对象优化。
但是在某些情况下,拥有函子的副本可能是一件好事。一个例子是使用如下的生成器函数对象:
#include <functional>
#include <cstdio>
template <typename F>
void func(F f) {
for (int i = 0; i < 5; i++) {
printf("Got %dn", (int)f());
}
}
int main() {
auto generator0 = [v = 0] () mutable { return v++; };
auto generator1 = generator0; generator1();
printf("Printing 0 to 4:n");
func(generator0);
printf("Printing 0 to 4 again:n");
func(generator0);
printf("Printing 1 to 5:n");
func(generator1);
}
如果我们使用F&&
代替,第二组打印将打印值5到9而不是0到4。我想这取决于应用程序/库的首选行为。
当使用函数指针时,我们也可能会遇到必须在invoke2
函数中使用额外的间接层的情况:
template <typename F>
void invoke1(F f) {
f();
}
template <typename F>
void invoke2(F&& f) {
f();
}
void fun();
void run() {
void (*ptr)() = fun;
void (&ref)() = fun;
invoke1(fun); // calls invoke1<void (*)()>(void (*)())
invoke1(&fun);// calls invoke1<void (*)()>(void (*)())
invoke1(ptr); // calls invoke1<void (*)()>(void (*)())
invoke1(ref); // calls invoke1<void (*)()>(void (*)())
invoke2(fun); // calls invoke2<void (&)()>(void (&)())
invoke2(&fun);// calls invoke2<void (*)()>(void (*&&)())
invoke2(ptr); // calls invoke2<void (*&)()>(void (*&)())
invoke2(ref); // calls invoke2<void (&)()>(void (&)())
}
语句invoke2(&fun);
将函数的地址放在堆栈上,然后将这个临时堆栈槽的引用(即地址)发送给invoke2
函数。然后,invoke2
函数必须使用额外的内存查找来读取堆栈上的引用,然后才能使用它。额外的间接也适用于invoke2(ptr)
。在所有其他情况下,函数的地址直接发送给invoke1
/invoke2
函数,不需要任何额外的间接。
这个例子展示了函数引用和函数指针之间一个有趣的区别。
当然,像内联这样的编译器优化可以很容易地摆脱这种额外的间接。
- 在 C++ 中将非指定类型作为参数传递的最佳方法?
- 读取大文件(>2GB)(文本文件包含以太网数据)并通过不同参数随机访问数据的最佳方法是什么?
- 将移出参数的最佳做法
- 生成包含给定类型的 N 个参数的可变参数列表的最佳方法?
- 使用作为参数返回的指针的最佳做法是什么
- 最佳做法是在方法中传递参数
- 将不同类型的多种参数发送到处理方法的最佳方法.C
- 将对象数组作为参数传递的最佳方法是什么
- 在函数中使用 const 引用参数访问函数中成员的最佳实践 C++.
- 第一个进程保持运行而后续进程仅传递参数的最佳 IPC 方法是什么?
- 最佳实践C :多种方法的重用默认参数
- 处理 2+ 模板参数的最佳方法
- 在C 中传递多个函数参数的最佳实践
- 将结构分为函数参数列表的最佳方法
- std::shared_ptr语言 - 将共享指针作为参数传递的最佳做法
- 将函数作为参数传递的最佳方式
- 派生类方法的默认参数的最佳实践
- 返回转发引用参数 - 最佳做法
- 具有多个特征对象作为参数的函数的最佳实践
- Win32 DLL导出函数参数最佳实践