在C++中传递函数

Passing functions in C++

本文关键字:传递函数 C++      更新时间:2023-10-16

假设我想编写一个调用空函数 100 次的函数。 这些实现中哪一个是最好的,为什么?

template<typename F>
void call100(F f) {
for (int i = 0; i < 100; i++)
f();
}
template<typename F>
void call100(F& f) {
for (int i = 0; i < 100; i++)
f();
}
template<typename F>
void call100(const F& f) {
for (int i = 0; i < 100; i++)
f();
}

template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; i++)
f();
}

还是有更好的实现?

关于 4 的更新

struct S {
S() {}
S(const S&) = delete;
void operator()() const {}
};
template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; i++)
f();
}
int main() {
const S s;
call100(s);
}

我会使用第一个(按值传递可调用的)。

如果调用方担心复制可调用对象的成本,则可以使用std::ref(f)std::cref(f)通过reference_wrapper传递它。

通过这样做,您可以为调用方提供最大的灵活性。

唯一的运行时成本

template<typename F>
void call100(F&& f) {
for (int i = 0; i < 100; ++i)
f();
}

是如果你以多种方式传递f,它可以有更多的版本(代码副本)。 使用 MSVC 或带有 ICF 的黄金链接器,这些副本只会花费编译时间,除非它们不同,如果它们不同,您可能希望保留它们。

template<typename F>
void call100(F f) {
for (int i = 0; i < 100; ++i)
f();
}

这个具有价值语义的优点;遵循取值的规则,除非你有充分的理由不这样做是合理的。std::ref/std::cref允许您使用持久引用来调用它,对于 prvalues C++17,保证省略将防止虚假副本。

开个玩笑,你可以做:

template<typename F>
void call100(F&& f) {
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
}

但这依赖于人们operator()&&过载,而没有人这样做。

我认为没有明确的答案:

  1. 第一个复制您传递的所有内容,这些内容可能很昂贵 用于捕获 lambda,但在其他方面提供了最大的灵活性:

    优点

    • 允许的常量对象
    • 允许(复制)可变对象
    • 可以省略副本(?

    缺点

    • 复制您提供的所有内容
    • 如果不复制它,则无法使用现有对象(例如可变 lambda)调用它
  2. 第二个不能用于常量对象。另一方面 它不复制任何内容并允许可变对象:

    优点

    • 允许的可变对象
    • 不复制任何内容

    缺点

    • 不允许常量对象
  3. 第三个不能用于可变的 lambda,所以有点 修改第二个。

    优点

    • 允许的常量对象
    • 不复制任何内容

    缺点

    • 不能使用可变对象调用
  4. 第四个不能用 const 对象调用,除非你复制 他们变得非常尴尬 与 lambdas.您也不能使用 它与预先存在的可变 lambda 对象不复制它或 从它移动(在此过程中丢失它)是类似的 限制为 1。

    优点

    • 如果需要副本,则通过强制(要求)移动语义来明确避免复制
    • 允许可变对象。
    • 允许的 const 对象(可变 lambda 除外)

    缺点

    • 不允许没有副本的常量可变 lambda
    • 您不能使用现有对象(例如可变 lambda)调用它

你有它。这里没有灵丹妙药,每个版本都有不同的优缺点。我倾向于第一个是默认值,但对于某些类型的捕获 lambda 或更大的可调用对象,这可能会成为一个问题。并且您不能使用可变对象调用 1) 并获得预期的结果。如其他答案所述,其中一些可以通过std::ref和其他操作实际T类型的方法来克服。根据我的经验,这些往往是非常讨厌的错误的来源,尽管当T与人们期望实现的不同时,即副本的可变性等。