在C ++中传递函数

时间:2019-04-14 11:37:59

标签: 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();
}

还是有更好的实现方法?

有关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);
}

3 个答案:

答案 0 :(得分:26)

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

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

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

答案 1 :(得分:10)

唯一的运行时成本

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可以用一个持久引用来称呼它,对于的prvalue,保证省略可以防止伪造的复制。

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

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

但这依赖于&&上有operator()个过载的人,没有人这样做。

答案 2 :(得分:8)

我认为没有明确的答案

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

    优点

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

    缺点

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

    优点

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

    缺点

    • 不允许使用const对象
  3. 第三个不能用于可变的lambda,因此略有变化 修改第二个。

    优点

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

    缺点

    • 不能用可变对象调用
  4. 除非复制,否则不能用const对象调用第四个对象 它们对lambda变得很尴尬。您也不能使用 它与预先存在的可变lambda对象而不复制它或 远离它(在过程中丢失),这是相似的 限制为1。

    优点

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

    缺点

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

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