std::function和std::bind:它们是什么,何时应该使用?这是一个关于IT技术的提问标题。

182

我知道functor是什么以及何时使用它们与std算法,但我不理解Stroustrup在C++11 FAQ中讲述的有关它们的内容。

是否有人可以解释一下std::bindstd::function是什么,何时应该使用它们,并为新手提供一些示例?

4个回答

270

std::bind是用于部分函数应用的。

也就是说,假设您有一个接受3个参数的函数对象f

f(a,b,c);

您想要一个新的函数对象,它只接受两个参数,并定义为:

g(a,b) := f(a, 4, b);

g 是函数 f 的 "偏应用":中间参数已经指定,还剩下两个参数。

您可以使用 std::bind 函数来获得 g

auto g = bind(f, _1, 4, _2);

这比实际编写函数类更简洁。在您提供的文章中有更多示例。通常在需要将函数对象传递给某个算法时使用它。您有一个函数或函数对象,它几乎可以完成您想要的工作,但比算法使用的更具配置性(即具有更多参数)。因此,您会将参数绑定到某些参数上,并将其余部分留给算法来填充:
// raise every value in vec to the power of 7
std::transform(vec.begin(), vec.end(), some_output, std::bind(std::pow, _1, 7));

在这里,pow 接受两个参数,并且可以将数字提高到 任何 次方,但我们只关心将数字提高到 7 次方。
作为一种偶尔使用的非部分函数应用,bind 还可以重新排列函数的参数:
auto memcpy_with_the_parameters_in_the_right_flipping_order = bind(memcpy, _2, _1, _3);

我不建议仅因您不喜欢API而使用它,但它具有潜在的实际用途,例如:

not2(bind(less<T>, _2, _1));

是一个小于等于函数(假设存在全序,等等)。这个例子通常不是必需的,因为已经有一个std::less_equal(它使用<=运算符而不是<,所以如果它们不一致,那么你可能需要这个函数,并且你可能还需要用棒子敲一下类的作者)。这种转换在使用函数式编程风格时会出现。


23
方便回调成员函数的方法:myThread=boost::thread(boost::bind(&MyClass::threadMain, this)) - rlduffy
20
很好的bind解释。但是std::function呢? - RedX
13
你的pow示例不能编译。由于pow是一个重载函数,你需要手动指定哪个重载函数。不能让调用结果的调用者自行推断绑定。例如:std::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7)); - M.M
5
非常清楚明白的解释,但有时候std::bind会与this用作第二个参数一起使用。您能详细说明这种用法吗? - Mendes
9
"_1"指的是std::placeholders::_1。我花了一些时间才找出为什么这不会编译。 - terryg
显示剩余3条评论

65

std::function和std::bind的主要用途之一是作为更通用的函数指针。 您可以使用它来实现回调机制。其中一个流行的场景是,您有一些需要花费很长时间才能执行完毕的函数,但您不希望等待其返回结果,那么您可以在单独的线程上运行该函数,并给它一个函数指针,在完成后回调它。

以下是一个示例代码,演示如何使用此功能:

class MyClass {
private:
    //just shorthand to avoid long typing
    typedef std::function<void (float result)> TCallback;

    //this function takes long time
    void longRunningFunction(TCallback callback)
    {
        //do some long running task
        //...
        //callback to return result
        callback(result);
    }

    //this function gets called by longRunningFunction after its done
    void afterCompleteCallback(float result)
    {
        std::cout << result;
    }

public:
    int longRunningFunctionAsync()
    {
        //create callback - this equivalent of safe function pointer
        auto callback = std::bind(&MyClass::afterCompleteCallback, 
            this, std::placeholders::_1);

        //normally you want to start below function on seprate thread, 
        //but for illustration we will just do simple call
        longRunningFunction(callback);
    }
};

12
这是一个很好的答案。我已经四处寻找这个答案了。谢谢@ShitalShah。 - terryg
你能否添加一个解释说明绑定为什么有助于使其更安全? - Steven Lu
1
我的错...我并不是想说它更“安全”。普通的函数指针也是类型安全的,但是std::function更通用,可以处理lambda表达式、上下文捕获、成员方法等。 - Shital Shah
bind(&MyClass::afterCompleteCallback, this, std::placeholders::_1)是什么意思?定义中有2个参数,void afterCompleteCallback(float result),可以解释一下吗? - user12450543
3
对于成员函数的函数指针,我们需要将“this”指针作为第一个参数传递。 - sanoj subran
为什么需要使用std::bind(),而不仅仅使用类似这样的方式:longRunningFunction(afterCompleteCallback);? - undefined

15

在提议将boost bind包含到C++库之后,std::bind被投票纳入库中,主要是偏函数特化的实现方式,您可以固定几个参数并在运行时更改其他参数。现在这是在C++中实现lambda表达式的库的方式。正如Steve Jessop所回答的那样。

现在C++11支持lambda函数,我不再觉得使用std::bind有任何诱惑力。我更愿意使用语言功能的柯里化(部分特化)而不是库功能。

std::function对象是多态函数。基本思想是能够互换地引用所有可调用的对象。

我会指向以下两个链接以获取更多细节:

C++11中的Lambda函数: https://www.variadic.xyz/2011/10/12/c11-lambda-having-fun-with-brackets/

C++中的可调用实体: https://www.variadic.xyz/2011/05/31/callable-entity/


7
如果没有lambda表达式,就不会有std::bind - 这两个功能都是在C++11中引入的。我们有bind1stbind2nd,它们是C++11 bind的简化版本。 - M.M
http://www.nullptr.me 域名已经不存在了。 - Jabberwocky

6

我曾经用它来创建一个C++插件线程池;由于该函数需要三个参数,你可以这样写:

假设你的方法签名如下:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

为了创建一个绑定三个参数的函数对象,你可以像这样做。
// a template class for converting a member function of the type int function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
public:
    explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
        :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

    //this operator call comes from the bind method
    _Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
    {
        return ((_P->*m_Ptr)(arg1,arg2,arg3));
    }
private:
    _Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

现在,为了绑定参数,我们需要编写一个绑定器函数。因此,它如下所示:
template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
    //This is the constructor that does the binding part
    binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
        :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}


        //and this is the function object 
        void operator()() const
        {
            m_fn(m_ptr,m1,m2,m3);//that calls the operator
        }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

还有一个用于使用binder3类的辅助函数 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

这里是如何调用它的

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
          &CTask::ThreeParameterTask), task1,2122,23 );

注意:f3();将调用方法task1->ThreeParameterTask(21,22,23);
更多详细信息-->http://www.codeproject.com/Articles/26078/A-C-Plug-in-ThreadPool-Design

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接