假设有这个一个场景,我们希望根据条件决定插入元素到list首或尾,条件判断一次,插入操作多次,例如二叉树,至少要处理左和右各一次。
普通的代码很简单,每次操作时,都判断一下,简化一下是一个三元表达式。
巧妙一点的,可以定义一个变量指定接口函数,根据条件设定指定的值,然后后面就可以直接用函数指针了,不再需要重复判断,实现代码如下:
list<int*> l;
typedef void (list<int*>::*fun_t)(int* const &);
bool back = true;
fun_t f = back ? (fun_t)&list<int*>::push_back : (fun_t)&list<int*>::push_front; //处理条件
int* v = nullptr;
v++;
(l.*f)(nullptr); //多次使用
(l.*f)(v); //多次使用
几个难点如下,假设相关理论知识已经具备,不解释。
1、类成员函数指针的使用
2、函数重载处理
3、模板类型为指针时注意事项
4、左值类型和右值类型
下文一改以往的思路,带着大家从最容易出错的起点上,一步一步改正确。部分确实是自己命中的,部分是自己推测大部分人会命中的。
1、直观上的写法
list<int> l;
auto f = &list<int>::push_back; //编译错误
int v = 1;
(l.*f)(0); //注意不能写成 l.*f(1);
(l.*f)(v);
return 0;
错误:error: unable to deduce ‘auto’ from ‘& std::list<_Tp, _Alloc>::push_back<int, std::allocator<int> >’
原因是push_back重载了,无法自动匹配类型,看一下push_back的定义如下
void push_back (const value_type& val); //适用于左值和右值
void push_back (value_type&& val); //仅适用于右值,移动
解决函数重载的办法是定义好对应参数的类型,使只唯一匹配重载的一个函数。
2、第二版代码
list<int> l;
typedef void (list<int>::*fun_t)(const int&); //定义类型
fun_t f = &list<int>::push_back;
int v = 1;
(l.*f)(0); //注意不能写成 l.*f(1);
(l.*f)(v);
如果使用重载的另一个函数,则代码如下,
list<int> l;
typedef void (list<int>::*fun_t)(int&&);
fun_t f = &list<int>::push_back;
int v = 1;
(l.*f)(0); //注意不能写成 l.*f(1);
(l.*f)(v); //编译报错, (l.*f)(move(v));正确
报错:error: cannot bind ‘int’ lvalue to ‘int&&’
函数接受一个右值,但传递的是左值,也就是函数要求传递的数据是值,不是变量本身。解决办法是使用std::move
3、似乎一切顺利,麻烦来了
将list类型改为指针int*,以下这个是没有问题的
list<int*> l;
typedef void (list<int*>::*fun_t)(int*&&);
fun_t f = &list<int*>::push_back;
int* v = nullptr;
v++;
(l.*f)(nullptr);
(l.*f)(move(v));
另一个报错
list<int*> l;
typedef void (list<int*>::*fun_t)(const int*&);
fun_t f = &list<int*>::push_back; //报错
int* v = nullptr;
v++;
(l.*f)(nullptr);
(l.*f)(move(v));
报错如下
/usr/include/c++/4.8.2/bits/stl_list.h:1020:7: note: candidates are: void std::list<_Tp, _Alloc>::push_back(std::list<_Tp, _Alloc>::value_type&&) [with _Tp = int*; _Alloc = std::allocator<int*>; std::list<_Tp, _Alloc>::value_type = int*]
push_back(value_type&& __x)
^
/usr/include/c++/4.8.2/bits/stl_list.h:1015:7: note: void std::list<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = int*; _Alloc = std::allocator<int*>; std::list<_Tp, _Alloc>::value_type = int*]
push_back(const value_type& __x)
意思是类型不匹配,有两个重载定义,一个是push_back(type&&),另一个是push_back(const type&),type是 int*
解释之前,先给出一下正确代码
list<int*> l;
typedef void (list<int*>::*fun_t)(int* const &); //注意一个const位置
fun_t f = &list<int*>::push_back;
int* v = nullptr;
v++;
(l.*f)(nullptr);
(l.*f)(v);
4、一切准备妥当,开始写逻辑吧,先来个错误的版本
list<int*> l;
typedef void (list<int*>::*fun_t)(int* const &);
bool back = true;
fun_t f = back ? &list<int*>::push_back : &list<int*>::push_front; //编译错误
int* v = nullptr;
v++;
(l.*f)(nullptr); //使用
(l.*f)(v); //使用
错误信息:error: address of overloaded function with no contextual type information
原因很简单,三元表达式先计算值,然后赋值给变量。在计算值时,并不知道会赋值给谁,对于重载,无法选取对应类型的,解决办法就是取地址时做一下转换。
5、完结,正确代码见开头。