一. 背景
在看硕哥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;
}
说明:
-
其中arg_list对应原可调用对象的参数列表。
-
其中下划线开头的数字是占位符,对应新返回的可调用对象的参数列表,第一个参数为_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算法(只接受单元的可调用对象)的话,有两种方式:
- 使用lambda表达式,虽然其参数只有一个elem,但是可以使用捕获列表,使用外部的局部变量threshold。
- 使用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时,必须将函数中用于表示执行对象的隐式形参转换成显式的。
这里先说明一下静态与非静态成员函数的一个区别:
-
对于类的非静态成员函数classA::func(),编译器会自动地为它扩展成非成员函数形式classA_func(classA *this),就是会默认地将其第一个参数设置为指向实例本身的指针, 从而让其变成普通函数(相较成员函数来讲)。所以非静态成员函数的参数列表与我们代码表面上的是不一样的。
-
而对于静态成员函数,可以直接转换为非成员函数形式的调用,不用额外地增加this指针,这也说明了为什么为其增加static声明后就可以直接用普通函数指针(相对成员函数指针来说)的原因。
-
所以在编译器内部,成员函数和非成员函数是几乎没有区别的(所以相较于非成员函数,成员函数并没有效率上的损失),因为对于成员函数,实际上也有转化为非成员函数这一步。
(参考 侯捷《深度探索C++对象模型》中文版141页 第4章)
那这个报错原因以及使用bind的原因都很好理解了,其根本原因就是因为多了this指针使参数列表不匹配。解决办法,我们只需要使用bind,将this指针包装进去就行了,也就是硕哥写的这两行:
server_.setConnectionCallback(
std::bind(&EchoServer::onConnection, this, _1));
这里将onConnection原本两个参数(this和_1)包装 为了单个参数的可调用对象,传递给了_setConnectionCallback。
好了,理解了~
睡觉
参考:https://blog.csdn.net/u010982765/article/details/79021426