在C ++中传递函数

And*_*zos 28 c++ c++17

假设我要编写一个调用null函数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();
}
Run Code Online (Sandbox Code Playgroud)

还是有更好的实施方案?

关于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);
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*low 26

我将使用第一个(按值传递可调用对象)。

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

这样,您可以为呼叫者提供最大的灵活性。

  • 还要注意,这是标准库在algorithmns库中的实现方式(例如,std :: for_each`,std :: count_if`) (6认同)
  • @Artyer虽然那些早于转发参考。 (5认同)

Yak*_*ont 10

唯一的运行时成本

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

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

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

这一优点是价值语义;并遵循取值原则,除非您有充分理由不合理。 std::ref/ std::cref让您使用持久引用来调用它,对于prvalues,保证省略将防止虚假复制。

开个玩笑,您可以这样做:

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

但这取决于人们的负担&&过重operator(),而没人能做到。


Res*_*ion 8

我认为没有明确的答案:

  1. 第一个复制您传递的所有内容,对于捕获lambda来说可能很昂贵,但其他方式则提供了最大的灵活性:

    优点

    • 允许使用const对象
    • 允许可变对象(已复制)
    • 可以删除副本(?)

    缺点

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

    优点

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

    缺点

    • 不允许使用const对象
  3. 第三个不能用于可变的lambda,因此对第二个做了一点修改。

    优点

    • 允许使用const对象
    • 不复制任何内容

    缺点

    • 不能用可变对象调用
  4. 除非使用const对象复制,否则不能用const对象调用第四个对象,这对于lambdas变得很尴尬。您也不能将其与预先存在的可变lambda对象一起使用,除非对其进行复制或从中移动(在过程中丢失)(与1类似)。

    优点

    • 如果需要复制,则通过强制(要求)移动语义来显式地避免复制
    • 允许使用可变对象。
    • 允许使用const对象(可变的lambda除外)

    缺点

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

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