【理论实践】指向类模板函数的指针的使用(以std::list为例)

来源:互联网 发布:漫画王软件 编辑:程序博客网 时间:2024/06/05 07:02

假设有这个一个场景,我们希望根据条件决定插入元素到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);


原因:模板参数const永远是修饰的是形参而不是类型,所以指针类型时, const TYPE p 实际是 TYPE const p。


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、完结,正确代码见开头。

原创粉丝点击