如何在C++ 11中传递和执行匿名函数作为参数?

Moh*_*kar 33 c++ visual-c++ c++11

我正在寻找的代码如下.

bool Func1(int Arg1, C++11LambdaFunc Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }
}
Run Code Online (Sandbox Code Playgroud)

稍后我将使用此代码.

Func1(12, [](int D) -> bool { ... } );
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 47

基本版本,用于头文件:

template<typename Lambda>
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}
Run Code Online (Sandbox Code Playgroud)

更复杂的版本,如果您想从实现中拆分接口(它有运行时成本):

bool Func1(int Arg1, std::function<bool(int)> Arg2){
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}
Run Code Online (Sandbox Code Playgroud)

std::function使用类型消除创建一个围绕你的拉姆达一个自定义创建的包装,然后暴露了使用非虚拟接口pImpl模式将其转发到自定义创建包装.1

或者,在较少技术方面,std::function<bool(int)>是一个类可以包装几乎任何你可以调用的函数,传递一个与传递一致的参数int,并返回与返回a兼容的东西bool.

通过a调用std::function的运行时间大致等于virtual函数调用(由上面的类型擦除引起),当你创建它时,它必须复制传入的函数对象(aka functor)的状态(这可能很便宜) - 无状态lambda,或通过引用捕获参数的lambda - 或者在其他一些情况下代价昂贵)并存储它(通常在免费商店或堆上,有成本),而纯模板版本可以"内联"调用点(即,不仅可以比函数调用成本低,编译器甚至可以优化函数调用并返回边界!)

第一示例的花式版本还处理一些极端情况更好一点点:(也必须在头文件内实现,或者在相同的翻译单元,因为它是使用)

template<typename Lambda>
bool Func1(int Arg1, Lambda&& Arg2){
  if(Arg1 > 0){
    return std::forward<Lambda>(Arg2)(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}
Run Code Online (Sandbox Code Playgroud)

它使用一种称为"完美转发"的技术.对于某些仿函数,这会产生与#1略有不同的行为(通常更正确的行为).

大多数改进来自&&参数列表中的使用:这意味着传入对仿函数的引用(而不是副本),节省了一些成本,并允许传入一个const或非仿const函数.

std::forward<Lambda>(...)变化将仅导致行为上的变化,如果有人使用一种相对较新的C++功能,允许方法(包括operator())覆盖上的右值/左值状态this指针.从理论上讲,这可能很有用,但我看到的基于rvalue状态实际覆盖的仿函数this0.当我写严肃的图书馆代码(tm)时,我会去打扰这个,但很少这样做.

还有一件事可以考虑.假设你想要一个返回的函数bool,或者一个返回的函数,void如果函数返回,void你想要像处理它一样对待它true.例如,在迭代某个集合时,您正在调用正在调用的函数,并且您希望选择性地支持早期暂停.该函数false在想要提前停止时返回,true或者void以其他方式返回.

或者,在更一般的情况下,如果您有多个函数覆盖,其中一个覆盖函数,而其他函数则在同一位置使用其他类型.

这是可能的,这是我要进入这里(使用智能适配器,或通过SFINAE技术).但是,你可能最好只创建两个不同的命名函数,因为所需的技术太重了.


1技术上std::function可以使用魔法仙尘来做它所做的事情,因为它的行为是由标准描述的,而不是它的实现.我正在描述一个简单的实现,它近似于std::function我与之交互的实现的行为.


And*_*owl 19

第一解决方案

您可以将您的Func1()函数设置为函数模板:

template<typename T>
bool Func1(int Arg1, T&& Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false; // <== DO NOT FORGET A return STATEMENT IN A VALUE-RETURNING
                  //     FUNCTION, OR YOU WILL GET UNDEFINED BEHAVIOR IF FLOWING
                  //     OFF THE END OF THE FUNCTION WITHOUT RETURNING ANYTHING
}
Run Code Online (Sandbox Code Playgroud)

然后你可以根据需要调用它:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}
Run Code Online (Sandbox Code Playgroud)

二解决方案:

如果您不想使用模板,则可以使用另一种方法(这将带来一些运行时开销)std::function:

#include <functional>

bool Func1(int Arg1, std::function<bool(int)> Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false;
}
Run Code Online (Sandbox Code Playgroud)

再次,这将允许您Func1()按照您想要的方式呼叫:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}
Run Code Online (Sandbox Code Playgroud)

  • @Yakk:嗯,我想在调用lambda或函数时没有区别,对吧?毕竟你没有转发任何东西.如果函子使用对`this`的引用(作为函数限定符,我的意思是`operator()(...)&&`),它*可以*与仿函数产生一些差别 - 但是......嘿,不是值得打扰IMO :) (2认同)

And*_*oss 11

对于那些口味更传统的人,请注意非捕获lambda可以转换为函数指针.所以你可以把上面的函数写成:

bool Func1(int Arg1, bool (*Arg2)(int)) { ... }
Run Code Online (Sandbox Code Playgroud)

它将适用于传统函数 lambdas.