读书笔记:C++11新特性:std::bind与成员函数

一. 背景

在看硕哥muduo库例子代码时遇到了一个似曾相识的函数bind:

EchoServer::EchoServer(muduo::net::EventLoop* loop,
                       const muduo::net::InetAddress& listenAddr)
  : server_(loop, listenAddr, "EchoServer")
{
  server_.setConnectionCallback(
      std::bind(&EchoServer::onConnection, this, _1));
  server_.setMessageCallback(
      std::bind(&EchoServer::onMessage, this, _1, _2, _3));
}

翻了书又学习了一下,写个笔记记录一下。

这个std::bind和网络编程里的那个bind完全不相关,不要把它们俩搞混了。

二. C++11新特性——std::bind

其一般调用形式如下:(C++ primer 中文版 第五版 354页)

        include <functional>
        auto newCallable = bind(callable, arg_list);

其中返回值newCallable是一个可调用对象,参数callable也是一个函数对象(函数指针),arg_list是逗号分隔的参数列表。
这个函数bind的作用是对原函数对象的参数列表进行“包装 ”,来将其适配成我们想要的参数列表形式。看代码:

例子1:

#include <iostream>
#include <functional>
using namespace std;
using std::placeholders::_1;//占位符
using std::placeholders::_2;


int diminish(int x, int y) {
    return x - y;
}

int main()
{
   //适配函数参数的次序
    cout << "diminish(100, 50) = " << diminish(100, 50) << endl;
    auto dim2 = bind(diminish, _2, _1);
    cout << "dim2(100, 50) = " << dim2(100, 50) << endl;// diminish(50, 100)
    
   //适配函数参数的个数
    cout << "diminish(100, 50) = " << diminish(100, 50) << endl;
    auto dim3 = bind(diminish, _1, 50);
    cout << "dim3(100) = " << dim3(100) << endl;// diminish(100, 50)
    
    return 0;
}

说明:

  1. 其中arg_list对应原可调用对象的参数列表。

  2. 其中下划线开头的数字是占位符,对应新返回的可调用对象的参数列表,第一个参数为_1,第二个为_2,…。使前时必须用using声明。

例如

    auto dim3 = bind(diminish, _1, 50);

_1对应diminish(x, y)中的x,50对应y。而dim3需要一个参数,对应于_1。dim3(100)就相当于diminish(100, 50)。

这个例子过于简单,以至于看起来有些多此一举,但是在一些情况下,这样做会方便很多。例如在泛型算法中,经常用到可调用对象。

例子2:

#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

using namespace std;
using std::placeholders::_1;
using std::placeholders::_2;

void printElemSmallertoT(int elem, int T) {
    if(elem < T) cout << elem << ", ";
}

int main()
{
    vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    //we want to find element smaller to Threshold
    int threshold;
    cin >> threshold;
    
    /*
    //方法1:使用lambda表达式
    for_each(vec.begin(), vec.end(), [threshold](int& elem){
        if(elem < threshold) cout << elem << ", ";
    });
    */
    //方法2:使用bind
    for_each(vec.begin(), vec.end(), bind(printElemSmallertoT, _1, threshold));
    
    cout << endl;
    return 0;
}

上面这个例子中,我们想要打印出vec中小于threshold的元素,如果要使用for_each算法(只接受单元的可调用对象)的话,有两种方式:

  1. 使用lambda表达式,虽然其参数只有一个elem,但是可以使用捕获列表,使用外部的局部变量threshold。
  2. 使用bind,将原本的二元函数包装成一元函数,也就是将printElemSmallertoT的第二个参数始终绑定为threshold,这样传给for_each的就是个单元函数了。

三. bind与类的成员函数

再看开头的代码:

EchoServer::EchoServer(muduo::net::EventLoop* loop,
                       const muduo::net::InetAddress& listenAddr)
  : server_(loop, listenAddr, "EchoServer")
{
  server_.setConnectionCallback(
      std::bind(&EchoServer::onConnection, this, _1));
  server_.setMessageCallback(
      std::bind(&EchoServer::onMessage, this, _1, _2, _3));
}

其中onConnection和_setConnectCallback的原型如下:

//TcpServer.h
  void setConnectionCallback(const ConnectionCallback& cb)
  { connectionCallback_ = cb; }

//echo.h
void EchoServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
  LOG_INFO << "EchoServer - " << conn->peerAddress().toIpPort() << " -> "
           << conn->localAddress().toIpPort() << " is "
           << (conn->connected() ? "UP" : "DOWN");
}

&EchoServer::onConnection是一个指向成员函数的指针,前面的&符号必不可少,否则会报错:

echo.cpp:19:45: error: reference to non-static member function must be called

这个原因是此函数不是静态成员函数,不能直接引用。(参见《C++ Primer》中文 版第五版739页)解决方法有两种,要么将其指定为static的,要么使用指向成员函数的指针。

转回正题,ConnectionCallback是一个用function生成的可调用对象,

typedef std::function<void (const TcpConnectionPtr&)> ConnectionCallback;

这里的onConnection和_setConnectCallback都是只接受一个参数的成员函数,那这里为什么还要使用bind呢?

试着去掉bind,把它换成原始方式:

EchoServer::EchoServer(muduo::net::EventLoop* loop,
                       const muduo::net::InetAddress& listenAddr)
  : server_(loop, listenAddr, "EchoServer")
{/*
  server_.setConnectionCallback(
      std::bind(&EchoServer::onConnection, this, _1));
*/
  server_.setConnectionCallback(&EchoServer::onConnection);//改为这一句
  server_.setMessageCallback(
      std::bind(&EchoServer::onMessage, this, _1, _2, _3));
}

编译报错:

echo.cpp:19:33: error: reference to type 'const muduo::net::ConnectionCallback' 
(aka 'const function<void (const shared_ptr<muduo::net::TcpConnection> &)>') 
could not bind to an rvalue of type 'void (EchoServer::*)(const 
muduo::net::TcpConnectionPtr &)'

翻译过来就是ConnectionCallback这个可调用对象不能绑定到TcpConnectionPtr这个右值类型。

好吧,还是不理解这句话。翻开书,在《C++ Primer》第五版中文版744页找到了答案:

19.4.3 将成员函数用作可调用对象
…与普通的函数指针不同,成员指针不是一个可调用对象,这样的指针不支持函数调用运算符。

746页:

…可以使用bind从成员函数生成一个可调用对象:

//选择范围中的每个string,并将其bind到empty的第一个隐式实参上
auto it = find_if(svec.begin(), svec.end(),
								 bind(&string::empty, _1));

和function类似的地方是,当我们使用bind时,必须将函数中用于表示执行对象的隐式形参转换成显式的。

这里先说明一下静态与非静态成员函数的一个区别:

  1. 对于类的非静态成员函数classA::func(),编译器会自动地为它扩展成非成员函数形式classA_func(classA *this),就是会默认地将其第一个参数设置为指向实例本身的指针, 从而让其变成普通函数(相较成员函数来讲)。所以非静态成员函数的参数列表与我们代码表面上的是不一样的。

  2. 而对于静态成员函数,可以直接转换为非成员函数形式的调用,不用额外地增加this指针,这也说明了为什么为其增加static声明后就可以直接用普通函数指针(相对成员函数指针来说)的原因。

  3. 所以在编译器内部,成员函数和非成员函数是几乎没有区别的(所以相较于非成员函数,成员函数并没有效率上的损失),因为对于成员函数,实际上也有转化为非成员函数这一步。

(参考 侯捷《深度探索C++对象模型》中文版141页 第4章)

那这个报错原因以及使用bind的原因都很好理解了,其根本原因就是因为多了this指针使参数列表不匹配。解决办法,我们只需要使用bind,将this指针包装进去就行了,也就是硕哥写的这两行:

  server_.setConnectionCallback(
      std::bind(&EchoServer::onConnection, this, _1));

这里将onConnection原本两个参数(this和_1)包装 为了单个参数的可调用对象,传递给了_setConnectionCallback。

好了,理解了~

睡觉

2019/07/24 01:21

参考:https://blog.csdn.net/u010982765/article/details/79021426

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值