boost::bind/function的索引占位符的实现

本文详细介绍了Boost库中的bind函数与占位符_1、_2的使用原理及其实现方式,包括如何通过模板元编程来实现索引占位符的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

boost::bind/function的索引占位符的实现

说明:如果代码中的对boost库的使用有误(如大小写不匹配,丢失连字符等),请勿奇怪。本文仅用于讲解,请把它们当成伪码。

所谓索引占位符,就是指在执行bind时,传入的参数不是具体的数值,而是一个能够起到索引作用的对象,该对象能够从传递给(bind函数返回的函数对象的)函数调用的实参中取得对应的数值,如:
int callback(int a, int b){return a + b;}
int result1 = bind(callback, 10, _1)(20);//callback(10, 20)
int result2 = bind(callback, _2, _1)(20, 10);//callback(10, 20)
上面表达式中的_1和_2就是占位符;对于int result2 = bind(callback, _2, _1)(20, 10);中的(20, 10),20在实参中的索引是1,10在实参中的索引是2,也就是说实参索引也就是实参的顺序,只不过该顺序从1开始。_1表示顺序号为1的实参,_2表示顺序号为2的实参。因此,在int result2 = bind(callback, _2, _1)(20, 10);中,_1对应于(20, 10)中的20,_2对应于(20, 10)中的10。
那么,_1、_2具体是如何进行索引的呢?
先看看_1、_2的定义(这些定义乃个人定义,并非来自boost,不过和boost类似):
template<int INDEX> struct TIndex{};
typedef TIndex<1> _1;
typedef TIndex<2> _2;
可以看到,_1、_2是类型;不过要注意,bind(callback, _2, _1)中的bind接受的是_1、_2对象,而不是_1、_2类型,它相当于:
_2 index2;
_1 index1;
bind(index2, index1);
在上一篇中,已经讲过,bind的返回值是一个函数对象。对于int result2 = bind(callback, _2, _1)(20, 10);这一表达式中的bind,其返回值类型类似于:
class TBoundFunctionObject
{
public:
 int operator()(int a, int b)
 {
  int parameter1 = ValueFromIndex(m_index1InParameters, a, b);
  int parameter2 = ValueFromIndex(m_index2InParameters, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 int (*m_callback)(int a, int b);
 TIndex<2> m_index1InParameters;
 TIndex<1> m_index2InParameters;
};
参考上一篇,将之改为模板:
template<typename TReturn, typename TArg1, typename TArg2, typename TIndex1, typename TIndex2> class TBoundFunctionObject
{
public:
 TReturn operator()(TArg1 a, TArg2 b)
 {
  TArg1 parameter1 = ValueFromIndex(m_index1InParameters, a, b);
  TArg2 parameter2 = ValueFromIndex(m_index2InParameters, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 TReturn (*m_callback)(TArg1 a, TArg2 b);
 TIndex1 m_index1InParameters;
 TIndex2 m_index2InParameters;
};
不同之处在于,该模板增加了表示索引占位符类型的模板参数TIndex1和TIndex2。其实这么说是不完整的,因为传递给bind的即可以是索引占位符,也可以是实际数值。为此,将上面的模板稍作更改:
template<typename TReturn, typename TArg1, typename TArg2, typename TBind1, typename TBind2> class TBoundFunctionObject
{
public:
 TReturn operator()(TArg1 a, TArg2 b)
 {
  TArg1 parameter1 = ValueFromIndex(m_bind1, a, b);
  TArg2 parameter2 = ValueFromIndex(m_bind2, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 TReturn (*m_callback)(TArg1 a, TArg2 b);
 TBind1 m_bind1;
 TBind2 m_bind2;
};
其中TBind1既可以是实际数值类型,也可以是_1、_2等。
现在,关键的地方来了,ValueFromIndex如何实现?
可以肯定的是,ValueFromIndex是一个函数模板,其返回值由其第一个参数决定:
 如果是实际数值,则返回实际数值;
 如果是_1,则返回第二个参数a;
 如果是_2,则返回第三个参数b。
其实现应当类似于:
template<typename TArg1, typename TArg2, typename TArg3> TReturn ValueFromIndex(TArg1 a1, TArg2 a2, TArg3 a3)
{
 if(IsParameter(a1)) return a1;
 if(Is_1(a1)) return a2;
 if(Is_2(a1)) return a3;
 throw exception("invalid a1.");
}
问题来了:
1. TReturn该如何定义?
2. 如果TArg2、TArg3和TArg1(在表示实际数值时)不同,上面的实现显然非法,因为一个函数不可能具有多个返回值类型。那么,该如何解决?
两个问题的解决办法当然是有,需要利用模板元编程。
我们知道三目运算符?:,但它无法应用于类型,boost::mpl中有一个if实现了该功能:
boost::mpl::if<bool, Type1, Type2>::type:如果第一个参数为true,则type表示Type1,否则表示Type2
实现原理(利用偏特化):
template<bool T1, typename Type1, typename Type2> struct if
{
 typedef Type1 type;
};
template<typename Type1, typename Type2> struct if<false>
{
 typedef Type2 type;
};
还可以利用偏特化实现switch类型的功能:
switch<int i, char, short, int>::type:如果i为1,则type表示short;如果为2,则type表示int;否则(default),type表示char。
template<int i, typename Type1, typename Type2, typename Type3> struct switch
{
 typedef Type1 type;
};
template<typename Type1, typename Type2, typename Type3> struct switch<1>
{
 typedef Type2 type;
};
template<typename Type1, typename Type2, typename Type3> struct switch<2>
{
 typedef Type3 type;
};
仿照该switch,第一个问题解决了,TReturn应该如下定义:
TSwitch<TBind, TArg1, TArg2>::type
template<typename TBind, typename TArg1, typename TArg2> struct TSwitch
{
 typedef TBind type;
};
template<typename TArg1, typename TArg2> struct TSwitch<_1>
{
 typedef TArg1 type;
};
template<typename TArg1, typename TArg2> struct TSwitch<_2>
{
 typedef TArg2 type;
};
对于第二个问题,方法类似:
template<typename TArg1, typename TArg2, typename TArg3> TSwitch<TArg1, TArg2, TArg3>::type ValueFromIndex(TArg1 a1, TArg2 a2, TArg3 a3)
{
 return a1;
}
template<typename TArg2, typename TArg3> TSwitch<_1, TArg2, TArg3>::type ValueFromIndex(_1 a1, TArg2 a2, TArg3 a3)
{
 return a2;
}
template<typename TArg2, typename TArg3> TSwitch<_2, TArg2, TArg3>::type ValueFromIndex(_2 a1, TArg2 a2, TArg3 a3)
{
 return a3;
}
但是很可惜,模板函数不支持偏特化,所以需要包装一下:
template<typename TArg1, typename TArg2, typename TArg3> struct ValueFromIndex
{
 static TSwitch<TArg1, TArg2, TArg3>::type Value(TArg1 a1, TArg2 a2, TArg3 a3)
 {
  return a1;
 }
};
template<typename TArg2, typename TArg3> struct ValueFromIndex<_1>
{
 static TSwitch<_1, TArg2, TArg3>::type Value(_1 a1, TArg2 a2, TArg3 a3)
 {
  return a2;
 }
};
template<typename TArg2, typename TArg3> struct ValueFromIndex<_2>
{
 static TSwitch<_2, TArg2, TArg3>::type Value(_2 a1, TArg2 a2, TArg3 a3)
 {
  return a3;
 }
};
相应修改TBoundFunctionObject:
template<typename TReturn, typename TArg1, typename TArg2, typename TBind1, typename TBind2> class TBoundFunctionObject
{
public:
 TReturn operator()(TArg1 a, TArg2 b)
 {
  TArg1 parameter1 = ValueFromIndex<TBind1, TArg1, TArg2>::Value(m_bind1, a, b);
  TArg2 parameter2 = ValueFromIndex<TBind2, TArg1, TArg2>::Value(m_bind2, a, b);
  return m_callback(parameter1, parameter2);
 }
private:
 TReturn (*m_callback)(TArg1 a, TArg2 b);
 TBind1 m_bind1;
 TBind2 m_bind2;
};
最后,还有一个问题需要注意,就是TBoundFunctionObject::operator()能够接受的参数数目需要随着传递给bind的实际数值的个数作出变动,如:
int result1 = bind(callback, 10, _1)(20);//TBoundFunctionObject::operator()必须接受1个参数
int result2 = bind(callback, _2, _1)(20, 10);//BoundFunctionObject::operator()必须接受2个参数
int result2 = bind(callback, _2, _2)(20, 10);//BoundFunctionObject::operator()必须接受2个参数(此处不符合boost中的规定,跳过_1直接使用了_2)
一个简单的解决办法是重载operator(),列出所有可能的情况,然后在各种情况中进行相应的判断
template<typename TReturn, typename TArg1, typename TArg2, typename TBind1, typename TBind2> class TBoundFunctionObject
{
public:
 TReturn operator()()//bind(callback, 10, 20)();
 {
  assert(TMaxIndex<TBind1, TBind2>::Value == 0);
  return m_callback(m_bind1, m_bind2);
 }
 template<tpyename TArg> TReturn operator()(TArg a)//bind(callback, 10, _1)(20);bind(callback, _1, 20)(10);bind(callback, _1, _1)(20);
 {
  assert(TMaxIndex<TBind1, TBind2>::Value == 1);
  TArg1 parameter1 = ValueFromIndex2<TBind1, TArg>::Value(m_bind1, a);
  TArg2 parameter2 = ValueFromIndex2<TBind2, TArg>::Value(m_bind2, a);
  return m_callback(parameter1, parameter2);
 }
 TReturn operator()(TArg1 a, TArg2 b)//bind(callback, _2, _1)(20, 10);bind(callback, _2, _2)(20, 10);
 {
  //如果要禁止bind(callback, _2, _2)(20, 10);,可以增加assert;不过该写法太繁琐,较好的方法参考下一篇
  //assert((TMaxIndex<TBind1>::Value == 1 && TMaxIndex<TBind1>::Value == 2) || (TMaxIndex<TBind1>::Value == 2 && TMaxIndex<TBind1>::Value == 1));
  assert(TMaxIndex<TBind1, TBind2>::Value == 2);
  TArg1 parameter1 = ValueFromIndex<TBind1, TArg1, TArg2>::Value(m_bind1, a, b);
  TArg2 parameter2 = ValueFromIndex<TBind2, TArg1, TArg2>::Value(m_bind2, a, b);
  return m_callback(parameter1, parameter2);
 }
};
ValueFromIndex2类似ValueFromIndex,但只接受两个参数:如果第一个参数是_1,返回第二参数;否则返回第一个参数
TMaxIndex<>:用来取得最大的占位符的最大值;如TMaxIndex<_1,_3,_2>::Value为3;TMaxIndex<SomeType>::Value为0
TMaxIndex的实现:
template<bool b, int i1, int i2> struct IfValue
{
 enum enumValue{Value = i1};
};
template<int i1, int i2> struct IfValue<false>
{
 enum enumValue{Value = i2};
};
template<int i1, int i2> struct TMax : public IfValue<(i1 > i2), i1, i2>
{
};

//一个参数版本的TMaxIndex
template<typename TType> struct TMaxIndex1
{
 enum enumValue{Value = 0};
};
template<> struct TMaxIndex1<_1>
{
 enum enumValue{Value = 1};
};
template<> struct TMaxIndex1<_2>
{
 enum enumValue{Value = 2};
};
//两个参数版本的TMaxIndex
template<typename TType1, typename TType2> struct TMaxIndex2
{
 enum enumValue{Value = TMax< TMaxIndex1<TType1>::Value, TMaxIndex1<TType2>::Value >::Value;
};
//三个参数版本的TMaxIndex
template<typename TType1, typename TType2, typename TType3> struct TMaxIndex3
{
 enum enumValue{Value = TMax< TMaxIndex2<TType1, TType2>::Value, TMaxIndex1<TType3>::Value >::Value;
};
//四个参数版本的TMaxIndex
template<typename TType1, typename TType2, typename TType3, typename TType4> struct TMaxIndex4
{
 enum enumValue{Value = TMax< TMaxIndex3<TType1, TType2, TType3>::Value, TMaxIndex1<TType4>::Value >::Value;
};
上面的TBoundFunctionObject还不够好:无法在编译期检测出调用参数数目错误的operator();对此,可以将assert(TMaxIndex<TBind1, TBind2>::Value == 0);更改为BOOST_STATIC_ASSERT(TMaxIndex<TBind1, TBind2>::Value == 0)等。
如果不允许重载operator(),该如何解决?留给大家思考吧。如果没有什么头绪,可以参考下几篇文章。

 

<think>我们正在讨论Boost库中bind函数的一个弃用警告,特别是关于占位符(placeholders)在全局命名空间的问题。 在较新版本的Boost(如1.73以上)中,Boost.Bind库已经弃用了在全局命名空间中定义占位符(如_1, _2等)的做法,转而要求使用命名空间boost::placeholders。 用户遇到的问题可能是:在代码中使用了_1, _2等占位符,但没有使用命名空间限定,导致编译器给出类似如下的警告: warning:boost::arg<I>& boost::operator+(boost::arg<I>, J) [with I = _1; J = int]’ is deprecated: The practice of declaring the Bind placeholders (_1, _2, ...) in the global namespace is deprecated. Please use <boost/bind/bind.hpp> + using namespace boost::placeholders, or define BOOST_BIND_GLOBAL_PLACEHOLDERS to retain the current behavior. 解决方案有两种: 1. 在代码中使用boost::placeholders命名空间,即明确使用boost::placeholders::_1, boost::placeholders::_2等,或者使用using namespace boost::placeholders; 2. 定义宏BOOST_BIND_GLOBAL_PLACEHOLDERS,保留旧行为(将占位符放在全局命名空间)。 但是,根据Boost官方文档,第一种方法是推荐的,因为第二种方法只是为了向后兼容,未来可能会被移除。 具体操作: 方法一(推荐): 在包含<boost/bind.hpp>之前,先包含<boost/bind/bind.hpp>,然后使用命名空间boost::placeholders。 例如: #include <boost/bind/bind.hpp> using namespace boost::placeholders; // 引入_1, _2等占位符 注意:有些代码可能只包含了<boost/bind.hpp>,而<boost/bind.hpp>会包含<boost/bind/bind.hpp>并自动定义BOOST_BIND_GLOBAL_PLACEHOLDERS(即保持旧行为)。但为了消除警告,我们应该直接包含<boost/bind/bind.hpp>并显式使用命名空间。 方法二(兼容): 在包含任何Boost头文件之前,定义宏BOOST_BIND_GLOBAL_PLACEHOLDERS。 例如: #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include <boost/bind.hpp> 这样,占位符就会像以前一样定义在全局命名空间。 另外,如果用户使用的是较新版本的Boost(>=1.73),并且代码中已经使用了boost::placeholders,那么应该不会出现警告。 如果用户使用的是muduo网络库,它可能在其代码中使用了boost::bind。如果muduo库本身没有更新以处理这个弃用问题,那么用户可能需要在自己的代码中采用上述方法之一。 但是,注意:如果用户在自己的代码中使用了boost::bind,那么应该按照上述方法修改自己的代码。如果警告来自muduo库内部,那么可能需要修改muduo的代码(但通常我们不建议修改第三方库,除非自己维护fork)。另一种方法是定义BOOST_BIND_GLOBAL_PLACEHOLDERS宏,这样也会影响muduo内部的boost::bind使用。 因此,建议用户在自己的代码中采用方法一,并在编译自己的代码定义宏BOOST_BIND_GLOBAL_PLACEHOLDERS(如果muduo库还没有适配)以避免muduo内部的警告。但是,如果muduo已经修复了这个问题(比如较新版本的muduo),那么就不需要了。 实际上,muduo的维护者已经注意到了这个问题,并在一些提交中进行了修复。例如,在muduo的代码中,他们可能会在包含boost/bind.hpp之前定义BOOST_BIND_GLOBAL_PLACEHOLDERS。所以,如果用户使用的是最新版的muduo,可能不会遇到这个警告。但如果用户遇到了,可以尝试在自己的编译环境中定义这个宏。 如何定义宏? 在编译命令中加入 -DBOOST_BIND_GLOBAL_PLACEHOLDERS 即可。 例如: g++ -DBOOST_BIND_GLOBAL_PLACEHOLDERS ... 或者在代码中(在包含boost头文件之前)定义。 总结步骤: 1. 如果用户在自己的代码中使用了boost::bind,并且使用了占位符(_1, _2等),则应该: 方法一(推荐): 修改代码,在包含boost/bind相关头文件的地方,改为: #include <boost/bind/bind.hpp> using namespace boost::placeholders; 方法二(临): #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include <boost/bind.hpp> 2. 如果警告来自muduo库内部,那么可以在编译整个项目定义宏BOOST_BIND_GLOBAL_PLACEHOLDERS(通过编译选项-D),或者等待muduo库更新。 注意:由于muduo库可能已经在其头文件中包含了boost/bind.hpp,所以用户需要在包含muduo头文件之前定义宏BOOST_BIND_GLOBAL_PLACEHOLDERS,这通常很难做到(因为muduo头文件可能被间接包含)。因此,更可靠的方法是在编译命令中定义该宏。 例如,在CMakeLists.txt中添加: add_definitions(-DBOOST_BIND_GLOBAL_PLACEHOLDERS) 或者,如果使用make,在Makefile的CFLAGS/CXXFLAGS中添加。 但是,请注意,定义这个宏可能会在自己的代码中重新启用全局占位符,这与我们在自己代码中使用命名空间的做法并不冲突。所以,可以同采用:在自己代码中使用命名空间,同定义宏以消除第三方库的警告。 然而,更推荐的做法是更新muduo到已经修复此问题的版本(如果存在)。根据muduo的GitHub仓库,这个问题在2019年10月左右就已经有修复(例如,在muduo的CMakeLists.txt中定义这个宏)。 因此,检查muduo的版本和编译选项可能是第一步。 如果用户不想更新muduo,那么就在自己的编译选项中定义宏。 最后,关于muduo的编译,用户之前的问题是关于编译示例找不到Makefile,我们已经解决了。现在这个问题是关于Boost bind的弃用警告,所以我们应该专注于解决这个警告。 总结:根据用户的具体情况,选择修改自己的代码和/或定义宏。</think>### 解决 Boost Bind 弃用警告:占位符全局命名空间问题 在编译使用 Boost 和 muduo 的 C++ 项目,遇到 `boost::bind` 的弃用警告通常是因为较新版本的 Boost(1.73+)改变了占位符的命名空间规则。以下是完整解决方案: --- #### 问题原因 Boost 1.73+ 默认要求**显式使用占位符命名空间**,弃用了全局命名空间的 `_1, _2` 等占位符: ```cpp // 旧用法 (触发警告) boost::bind(callback, _1, 123); // 新用法 (正确) boost::bind(callback, boost::placeholders::_1, 123); ``` [^1] --- #### 解决方案 ##### 方法1:显式声明命名空间(推荐) 在代码顶部添加命名空间声明: ```cpp #include <boost/bind/bind.hpp> // 添加以下声明 using namespace boost::placeholders; // 允许使用 _1, _2 等简 // 使用示例 boost::bind(&MyClass::handler, this, _1, _2); ``` ##### 方法2:全局兼容模式(临方案) 在编译定义宏(不推荐长期使用): ```bash # CMakeLists.txt 中添加 add_definitions(-DBOOST_BIND_GLOBAL_PLACEHOLDERS) # 或 g++ 编译添加 g++ -DBOOST_BIND_GLOBAL_PLACEHOLDERS ... ``` ##### 方法3:完整限定占位符(最安全) ```cpp boost::bind(&MyClass::handler, this, boost::placeholders::_1, boost::placeholders::_2); ``` --- #### muduo 项目特殊处理 如果警告来自 muduo 源码: 1. 更新 muduo 到最新版(已修复此问题): ```bash git pull origin master ``` 2. 或手动修改 muduo 代码: ```cpp // 在 muduo 源码中搜索 #include <boost/bind.hpp> // 在下方添加: using namespace boost::placeholders; ``` --- #### 验证修复 重新编译后检查警告是否消失: ```bash make clean make VERBOSE=1 # 显示详细编译命令 ``` 观察输出中是否还有 `deprecated-declarations` 相关警告。 --- #### 原理说明 - Boost 1.73+ 将占位符移入 `boost::placeholders` 命名空间[^1] - 旧全局命名空间用法会导致编译警告 - `BOOST_BIND_GLOBAL_PLACEHOLDERS` 是向后兼容的临方案 - muduo 在 2020 年后已修复此问题(commit 6d4b4c5) ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值