目录
一、ref(已入C++标准)
1.包装器reference_wrapper
2.工厂函数
3.操作包装?
4.与C++标准库对比?
二、bind(已入C++标准)
1.基本形式
2.占位符
3.操作普通函数(自由函数)
4.操作成员函数
5.操作成员变量
6.操作函数对象
7.与C++标准库对比
8.扩展
(1)存储bind表达式
(2)使?ref库
(3)绑定重载函数
一、ref(已入C++标准)
????????C++标准库和Boost中的算法?量使?了函数对象作为判断式或谓词参数,?这些参数都是传值语义,算法或函数在内部保留函数对象的拷?并使?。
?????????般情况下,传值语义都是可?的,但也有很多特殊情况:作为参数的函数对象拷?代价过?(具有复杂的内部状态),不希望拷?对象(内部状态不应该被改变),甚?拷?是不可?的(noncopyable、singleton)。
????????ref库定义了?个很?、很简单的引?类型的包装器reference_wrapper。
1.包装器reference_wrapper
????????reference_wrapper的构造函数接收类型T的引?类型,内部使?指针存储指向t的引?,构造出?个reference_wrapper对象,从?包装了引?。(请注意:reference_wrapper的构造函数被声明为explicit,因此必须在创建reference_wrapper对象时就初始化赋值,如同使??个引?类型的变量。)
????????get()和get_pointer()这两个函数分别返回存储的引?和指针,相当于解开对存储的包装。
????????reference_wrapper的?法有些类似C++中的引?类型(T&),它就像被包装对象的?个别名。但它只有在使?T的语境下才能够执?隐式转换,其他的情况下则需要调?类型转换函数或get( )函数才能真正地操作被包装对象。此外,reference_wrapper?持拷?构造和赋值,?引?类型是不可赋值的。
#include <boost/ref.hpp>
std::string name = "Tom";
boost::reference_wrapper<std::string> str(name); //包装器str对name进行包装
*str.get_pointer() = "Jerry";
if (str.get() == name)
{
std::cout << str.get() << std::endl; //Jerry
}
2.工厂函数
????????reference_wrapper的名字过?,?它声明包装对象很不?便,因此ref库提供了两个便捷的??函数ref( )和cref( ),利?它们可以通过推导参数类型很容易地构造包装对象。
????????ref( )和cref( )会根据参数类型?动推导?成正确的reference_wrapper<T>对象,ref( )产?的类型是T,?cref( )产?的类型是T const。
auto str_ref = boost::ref(name);
auto const_str_ref = boost::cref(name);
std::cout << "name类型:" << typeid(name).name() << std::endl;
//name类型:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
std::cout << "str_ref类型:" << typeid(str_ref).name() << std::endl;
//str_ref类型:class boost::reference_wrapper<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >
std::cout << "const_str_ref类型:" << typeid(const_str_ref).name() << std::endl;
//const_str_ref类型:class boost::reference_wrapper<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const >
3.操作包装?
????????ref库运?模板元编程技术提供两个特征类:is_reference_wrapper和unwrap_reference,?于检测reference_wrapper对象:
- is_reference_wrapper<T>::value 可以判断T是否被包装。
- unwrap_reference<T>::type 表明了T的真实类型(?论它是否经过包装)。
if (boost::is_reference_wrapper< decltype(str_ref)>::value)
{
std::cout << "str_ref原始类型:" << typeid(boost::unwrap_reference<decltype(str_ref)>::type).name() << std::endl;
//str_ref原始类型:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
}
?????????由函数unwrap_ref( )为解开包装提供了简便的?法,它利?unwrap_reference<T>直接解 开reference_wrapper的包装(如果有的话),返回被包装对象的引?。
std::vector<std::string> vec{ "hello","world" };
auto vec_ref = boost::ref(vec);
if (boost::is_reference_wrapper<decltype(vec_ref)>::value)
{
std::cout << "vec_ref原始类型:" << typeid(boost::unwrap_reference<decltype(vec_ref)>::type).name() << std::endl;
//vec_ref原始类型:class std::vector<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class std::allocator<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >
boost::unwrap_ref(vec_ref).insert(vec_ref.get().begin(), "said");
}
for (auto item : vec)
{
std::cout << item << " ";//said hello world
}
? ? ? ? 直接对?个未包装的对象使?unwrap_ref( )也是可以的,它将直接返回对象?身的引?。unwrap_ref( )的这个功能很有?,可以把unwrap_ref( )安全地?在泛型代码中,从?不必关?对象的包装特性,它总能够正确地操作对象。
注意下方代码,boost::unwrap_ref(sex)返回sex的引用,使用auto时需要加上&。(c++11 auto 推导规则:如果右边是引用类型,auto推导时会抛弃引用,直接推导成原始类型)
decltype、typeid、auto的区别如下:
- decltype:编译时推导(更通用),推导时不会抛弃引用。
- typeid:运行时推导。typeid()会返回type_info对象,该对象的成员函数name()返回类型名称;成员函数hash_code()可以用来对变量类型进行比较。
- auto:编译时推导,推导时会抛弃引用。
std::string sex = "女";
boost::unwrap_ref(sex)="男";
std::cout << sex << std::endl;//男
auto& ref_sex = boost::unwrap_ref(sex);
ref_sex = "男";
std::cout << sex << std::endl;//男
4.与C++标准库对比?
????????ref将对象包装为引?语义,降低了复制的代价,使引?的?为更像对象(因为对象更有?、更强 ?),可以让容器安全地持有被包装的引?对象,可以称其为“智能引?”。因此它被收?了C++ 标准。
???????? std::reference_wrapper是由boost.ref库演变?来的,所以它们两者?常相似,但它们也有少 量的不同,最?的区别是std::reference_wrapper?持调?操作符——这使得我们可以包装? 个函数对象的引?并将其传递给标准库算法。
二、bind(已入C++标准)
????????bind是对C++标准中函数适配器bind1st/bind2nd的泛化和增强,可以适配任意的可调?对象,包 括函数指针、函数引?、成员函数指针、函数对象和lambda表达式。 bind远远地超越了STL中的函数绑定器bind1st/bind2nd,它最多可以绑定9个函数参数,?且 bind对被绑定对象的要求很低,它可以在没有result_type内部类型定义的情况下完成对函数对象的绑定。
????????bind并不是?个单独的类或函数,?是?常庞?的家族,依据绑定的参数个数和要绑定的调?对 象类型不同,bind总共有数?个不同的重载形式,但它们的名字都叫作bind,编译器会根据具体 的绑定代码?动推导要使?的正确形式。
1.基本形式
????????bind接收的第?个参数必须是?个可调?对象f,可以是函数、函数指针、函数对象或成员函数指 针,之后bind最多接收9个参数,参数的数量必须与f的参数数量相等,这些参数将被传递给f作为 ?参。
????????绑定完成后,bind会返回?个函数对象,它内部保存了f的拷?,具有operator(),返回值类型被?动推导为f的返回值类型。在发?调?时,这个函数对象将把之前存储的参数转发给f完成调?。
2.占位符
????????bind的真正威?在于它的占位符,它们分别被定义为_1、_2、_3?直到_9,位于?个匿名名字空间。占位符可以取代bind中参数的位置,在发?函数调?时才接收真正的参数。占位符的名字表示它在调?式中的顺序,?绑定表达式对顺序没有要求,_1不?定要第?个出现,也不?定只出现?次。
3.操作普通函数(自由函数)
????????bind可以绑定普通函数,使?函数名或函数指针。我们必须在绑定表达式中提供函数要求的所有参数,?论是真实参数还是占位符均可以。 占位符可以出现也可以不出现,占位符出现的顺序和数量也没有限定,但不能使?超过函数参数数量的占位符。
????????格式:boost::bind(函数名, 参数1,参数2,...)
int sum(int a, float b)
{
float sum = a + b;
std::cout << "[自由函数sum]两个参数和:" << a << "+" << b << "=" << sum << std::endl;
return sum;
}
//StudyRefClass.h文件
#include <boost/bind.hpp>
class StudyRefClass
{
public:
void TestBind();
};
//StudyRefClass.cpp文件
void StudyRefClass::TestBind()
{
//对自由方法来说,直接boost::bind(函数名, 参数1,参数2,...)
boost::bind(sum, 2, 5.1)(); //[自由函数sum]两个参数和:2+5.1=7.1
boost::bind(sum, _2, _1)(5.1, 2);//[自由函数sum]两个参数和:2+5.1=7.1
}
bind同样可以绑定函数指针:?
int (*a)(int, float) = ∑
boost::bind(a, 2, 5.1)();
decltype(&sum) b = ∑
boost::bind(b, 2, 5.1)();
4.操作成员函数
????????类的成员函数不同于普通函数,因为成员函数指针不能直接调?operator(),它必须先被绑定到?个对象或指针上,然后才能得到this指针进?调?成员函数。因此bind需要“牺牲”?个占位符的位置,要求?户提供?个类的实例、引?或指针,通过对象作为第?个参数来调?成员函数,这意味着使?成员函数时最多只能绑定8个参数。
? ? ? ? 格式:boost::bind(&类名::方法名,类实例指针,参数1,参数2,...)
????????我们必须在成员函数前加上取地址操作符&,表明这是?个成员函数指针,否则会?法通过编译,这是与绑定普通函数的?处??的不同。
//--------------------StudyRefClass.h头文件
#include <boost/bind.hpp>
class StudyRefClass
{
public:
void TestBind();
private:
int Add(int a, float b);
int Sum(int a, float b);
void Add(int a, float b, double c);
};
//---------------------StudyRefClass.cpp文件
int StudyRefClass::Add(int a, float b)
{
float sum = a + b;
std::cout << a << "+" << b << "=" << sum << std::endl;
return sum;
}
int StudyRefClass::Sum(int a, float b)
{
float sum = a + b;
std::cout <<"[类函数sum]两个参数和:"<<sum << std::endl;
return sum;
}
void StudyRefClass::Add(int a, float b, double c)
{
double sum = (double)a + b + c;
std::cout << a << "+" << b << "+" << c << "=" << sum << std::endl;
}
void StudyRefClass::TestBind()
{
//对类方法(成员函数)来说,直接boost::bind(&类名::方法名,类实例指针,参数1,参数2)
boost::bind(&StudyRefClass::Add, this, 2, 5.1)(); //2+5.1=7.1
boost::bind(&StudyRefClass::Add, this, 2, 5.1, 6.2)(); //2+5.1+6.2=13.3
boost::bind(&StudyRefClass::Add, this, _1, _3, _2)(2, 6.2, 5.1); //2+5.1+6.2=13.3 //a=2, b=5.1, c=6.2
boost::bind(&StudyRefClass::Add, this, _2, _3, _2)(2, 6.2, 5.1); //2+5.1+6.2=13.3 //a=6, b=5.1, c=6.2
}
????????使?bind搭配标准算法 for_each可以?来调?容器中的对象。
template<typename InputIterator, typename Function>
Function for_each(InputIterator beg, InputIterator end, Function f) {
while(beg != end)
f(*beg++);
}
//StudyRefClass.h头文件
#include <boost/bind.hpp>
#include <algorithm>
class StudyRefClass
{
public:
void TestBind();
private:
struct point {
int x, y;
point(int a = 0, int b = 0) :x(a),y(b)
{
}
void print()
{
std::cout << x << "," << y << std::endl;
}
};
};
//StudyRefClass.cpp文件
void StudyRefClass::TestBind()
{
std::vector<point> points(10);
for (size_t i = 0; i < 10; i++)
{
point p(i, i + 1);
points[i] = p;
}
std::for_each(points.begin(), points.end(), boost::bind(&point::print, _1)); //此处占位符即类实例指针:point对象:points[0]...
//输出:
//0,1
//1,2
//...
}
?对比下方代码:
MyClass::PointerFn( Element * e ) { ... }
MyClass::Function()
{
std::vector< Element * > elements;
std::for_each( elements.begin(),elements.end(),boost::bind(&MyClass::PointerFn,boost::ref(*this),_1));
}
5.操作成员变量
????????bind的另?个对类的操作是它可以绑定public成员变量,它就像是?个选择器,其?法与绑定成员函数类似,只需要像使??个成员函数?样去使?成员变量名。
std::vector<point> vec_point(10);
for (size_t i = 0; i < 10; i++)
{
point p(i, i + 1);
vec_point[i] = p;
}
std::vector<int> vec_int(10);
//将vec_point列表中的point的x坐标存入vec_int中 ,boost::bind(&point::x, _1):取出point对象的x变量
std::transform(vec_point.begin(), vec_point.end(), vec_int.begin(), boost::bind(&point::x, _1));
//遍历vec_int
for (auto item : vec_int)
{
std::cout << item << ","; //0,1,2,3,4,5,6,7,8,9,
}
std::cout << std::endl;
? transform 是用来做转换的。转换有两种:一元转换和二元转换。
????????一元转换是对容器给定范围内的每个元素做某种一元运算后放在另一个容器里。只涉及一个参与转换运算的容器。有4个参数:前2个指定要转换的容器的起止范围,第3个参数是结果存放容器的起始位置,第4个参数是一元运算。
????????二元转换是对两个容器给定范围内的每个元素做二元运算后放在另一个容器里。涉及两个参与转换运算容器。有5个参数:前2个指定参与转换的第1个容器的起止范围,第3个参数是转换的第2个容器的起始位置,第4个参数是结果存放容器的起始位置,第5个参数是二元运算。
????????使?bind,可以实现SGISTL/STLport中的?标准函数适配器select1st和select2nd的功能,直接选择出pair对象的first和second成员。
std::pair<int, std::string> _pair = { 1,"hello" };
std::cout << boost::bind(&std::pair<int, std::string>::first, _pair)() << std::endl; //1
std::cout << boost::bind(&std::pair<int, std::string>::second, _pair)() << std::endl; //hello
6.操作函数对象
????????bind不仅能够绑定函数和函数指针,也能够绑定任意的函数对象,包括标准库中的所有预定义的函数对象。 如果函数对象有内部类型定义result_type,那么bind可以?动推导出返回值类型,其?法与绑定普通函数?样。但如果函数对象没有定义result_type,则需要在绑定形式上进?改动,?模板参数指明返回类型。(在编写??的函数对象时,最好遵循规范为它们增加内部的 typedef result_type,这将使函数对象与许多其他标准库和Boost库组件良好地配合?作)
std::cout << boost::bind(std::plus<int>(), _1, _2)(1, 10) << std::endl; //11
std::cout << std::plus<int>()(1, 10) << std::endl; //11
函数对象:
std::greater<...> 是一个类,而不是一个函数。这个类已经重载了operator() ,但你需要一个类的对象来调用该运算符。所以你应该创建一个类的实例,然后调用它:
cout << (std::greater<int>()(3, 2)? "TRUE":"FALSE") << endl;
// #1 #2
此处第一对括号(#1 )创建std::greater<int> 的实例,#2 调用std::greater<int>::operator()(const int&, const int&) 。
?7.与C++标准库对比
????????C++标准使?可变参数模板和完美转发简化了bind的定义(C++11.20.8.9),可以?持绑定任意数量的参数。
????????std::bind的?法与boost::bind完全相同。但为了避免冲突,标准占位符位于std:: placeholder名字空间,其代码?需要?“std::placeholders::_1”的形式,略微不太?便。
???????? C++标准还提供了语?级别的lambda表达式(C++11.5.1.2),它可以就地声明匿名函数对象, 其?法?常灵活。lambda表达式在某种程度上也可以代替bind,捕获列表“[...]”相当于绑定的变 量,函数参数列表“(...)”则相当于bind的占位符。
8.扩展
(1)存储bind表达式
????????很多时候我们需要把写好的bind表达式存储起来,以便稍后再次使?,但bind表达式?成的函数对象的类型声明?常复杂,通常?法写出正确的类型,因此可以使?auto来辅助我们写出正确的类型。
auto fun = boost::bind(std::plus<int>(), _1, _2);
std::cout << fun(10, 20) << std::endl; //30
std::cout << fun(20, 30) << std::endl; //50
(2)使?ref库
????????bind采?拷?的?式存储绑定函数对象和参数,这意味着绑定表达式中的每个变量都会有?份拷?,如果函数对象或参数很?、拷?代价很?,或者?法拷?,那么bind的使?就会受到限制。 因此bind库可以搭配ref库使?,ref库包装了对象的引?,可以让bind存储对象引?的拷?,从?降低拷?的代价。但这也带来了?个隐患,因为有时候bind的调?可能会延后很久,程序员必须保证bind被调?时引?是有效的。如果调?bind时引?的变量或函数对象被销毁了,那么就会发 ?未定义?为。
(3)绑定重载函数
????????直接使?函数名的绑定?式存在?点局限,如果程序?有若?个同名的重载函数,那么bind就?法确定要绑定的具体函数,导致编译错误。?个解决?法是?typedef定义函数指针类型,再使?函数指针变量明确要绑定的函数。但这个?法对于模板函数?效,因为我们很难写出?个准确的模板函数指针类型,这时我们只能使?lambda表达式来变通地“绑定”。
|