本篇文章基于gcc中标准库源码剖析一下标准库中的模板类pointer_traits
,并且以此为例理解一下traits技法。
说明一下,我用的是gcc7.1.0编译器,标准库源代码也是这个版本的。
还是先看一下思维导图,如下:
1. 指针萃取器pointer_traits说明
首先说明一下哈,官方并没有指针萃取器这个名称,其实pointer_traits
是类模板,它是c++11以后引入的,可以通过传入的重绑定模板类型得到相应的指针类型,比较官方的描述是:pointer_traits
类模板提供标准化方法,用于访问类指针类型的某些属性。
那么为什么要把这个pointer_traits
拿出来单独说明一下呢,因为类似之前的内存分配器一样,它是stl中某些容器的使用前提,在讲容器的时候,绕不开它,所以先把它搞清楚了有助于后续的学习和理解。
为什么要叫指针萃取器呢,我理解它类似于内存萃取器allocator_traits
,都是根据模板参数去得到某种类型,并且traits也有萃取的意思,所以我这里就叫指针萃取器了。
2. 指针萃取器源代码分析
类模板pointer_traits
在标准库中有两个版本,一个特化版本,一个非特化版本,源代码都在bits/ptr_traits.h
头文件中,当然实际使用的时候它是被包含在头文件memory
中的。
2.1 非特化pointer_traits
我们先分析一下非特化版本的源代码,如下:
//pointer_traits类模板 template<typename _Ptr> struct pointer_traits { private: template<typename _Tp> using __element_type = typename _Tp::element_type; template<typename _Tp> using __difference_type = typename _Tp::difference_type; template<typename _Tp, typename _Up, typename = void> struct __rebind : __replace_first_arg<_Tp, _Up> { }; //如果__void_t参数里面类型存在则直接使用下面这个结构体,否则使用上面那个 template<typename _Tp, typename _Up> struct __rebind<_Tp, _Up, __void_t<typename _Tp::template rebind<_Up>>> { using type = typename _Tp::template rebind<_Up>; }; public: using pointer = _Ptr; using element_type = __detected_or_t<__get_first_arg_t<_Ptr>, __element_type, _Ptr>; using difference_type = __detected_or_t<ptrdiff_t, __difference_type, _Ptr>; template<typename _Up> using rebind = typename __rebind<_Ptr, _Up>::type; static _Ptr pointer_to(__make_not_void<element_type>& __e) { return _Ptr::pointer_to(__e); } static_assert(!is_same<element_type, __undefined>::value, "pointer type defines element_type or is like SomePointer<T, Args>"); };
对于这段代码,其实初看起来是有点懵的,但是万变不离其宗,一个类被定义出来,最后是给别人使用的,所以对于类类型而言,我们只要搞懂它的公共成员都有些什么作用,那大概也就知道这个类的作用了。
这里需要说明一下__detected_or_t
的作用,它也是一个类型模板,声明如下:
template<typename _Default, template<typename...> class _Op, typename... _Args> using __detected_or_t = typename __detected_or<_Default, _Op, _Args...>::type;
作用是如果_Op<_Args...>
是一个有效的类型,那这个类型就是_Op<_Args...>
,否则就是_Default
。
那么对于类模板pointer_traits
,它的公共成员作用如下:
- pointer,这个其实就是模板参数
_ptr
的一个别名; - element_type,也是一个别名,如果
_ptr::element_type
这个类型存在,则它就是_ptr::element_type
这个类型,如果_ptr::element_type
这个类型不存在,但是_ptr
是一个模板特化,则它就是_ptr
,否则就是__undefined
,其实就是无意义类型了; - difference_type,也是一个别名,如果
_ptr::difference_type
这个类型存在,则它就是_ptr::difference_type
,否则就是ptrdiff_t
类型; - template<typename _Up>using rebind,它是一个类型别名模板,由类
pointer_traits
的模板参数和rebind的模板参数一起决定最终到底是什么类型,若_ptr::rebind<_Up>
这个类型存在则它就是_ptr::rebind<_Up>
,否则根据类型模板__replace_first_arg
的实现,若_ptr
是模板特化_Template<_Tp, _Types...>
,则它是_Template<_Tp, _Types...>
,否则就没有类型; - pointer_to,它是一个静态成员函数,调用模板类型的pointer_to函数,所以具体什么作用取决于
_ptr
的实现,但根据字面意思应该是获取element_type
类型对象的地址。
所以总的来看,说白了类模板pointer_traits
其实就是用于获取模板参数_ptr
的某些类型属性,那从这里反推一下,也能知道这个模板参数类型需要具有一些什么属性。
2.2 特化pointer_traits
接下来看一下特化类模板pointer_traits
的源代码实现:
template<typename _Tp> struct pointer_traits<_Tp*> { typedef _Tp* pointer; //为特化类型取个别名 typedef _Tp element_type; //为模板类型取别名 typedef ptrdiff_t difference_type; template<typename _Up> using rebind = _Up*; static pointer pointer_to(__make_not_void<element_type>& __r) noexcept { return std::addressof(__r); } };
对于特化类型,它的公共成员与非特化其实是一致的,只是它是为_Tp*
类型提供的特化,对于其他公共成员,这里比较简单,就不再多说了,重点再看一下template<typename _Up> using rebind
这个类型别名模板,它直接获取一个_Up*
类型的指针,结合整体来看,它的作用就是:重绑定类型成员模板别名,使得可以由指向 _Tp
的指针类型,获取指向 _Up
的指针类型。
源代码分析完以后,貌似有点印象了,但是我们具体应该怎么使用呢?
3. 指针萃取器的简单使用
我们先写一段例子代码,如下:
#include <memory> #include <iostream> #include <typeinfo> #include <cxxabi.h> //将gcc编译出来的类型翻译为真实的类型 const char* GetRealType(const char* p_szSingleType) { const char* szRealType = abi::__cxa_demangle(p_szSingleType, nullptr, nullptr, nullptr); return szRealType; } int main() { using ptr = typename std::pointer_traits<int*>::template rebind<double>; ptr p1; const std::type_info &info = typeid(p1); std::cout << GetRealType(info.name()) << std::endl; return 0; }
上面这个例子很显然用到了特化的pointer_traits
,并且用的rebind属性,由指向int的指针类型获得了指向double的指针类型,代码输出如下:
double*
看上面的代码,我们还是不知道pointer_traits
到底有啥作用,并且看起来是把简单的类型搞复杂了,但有一点,当我们不知道确切类型的时候,使用这个标准模板类获取指针类型还是蛮方便的,这一点在标准库的deque
容器中就有使用。
而对于非特化的pointer_traits
,看一下下面这段代码:
#include <memory> #include <iostream> #include <typeinfo> #include <cxxabi.h> #include <string> struct test_traits { using element_type = int; using difference_type = double; }; struct test_traits2 { using element_type = std::string; using difference_type = size_t; }; const char* GetRealType(const char* p_szSingleType) { const char* szRealType = abi::__cxa_demangle(p_szSingleType, nullptr, nullptr, nullptr); return szRealType; } int main() { using type1 = typename std::pointer_traits<test_traits>::element_type; using type2 = typename std::pointer_traits<test_traits2>::difference_type; const std::type_info &info = typeid(type1); std::cout << GetRealType(info.name()) << std::endl; const std::type_info &info2 = typeid(type2); std::cout << GetRealType(info2.name()) << std::endl; return 0; }
说白了,从这里看pointer_traits
的作用就是得到某些类型的属性,这个在类型未知的时候就比较有用,比较典型的用法是在标准库的allocator_traits
类模板里面,我们之前说过,allocator_traits
是内存萃取器,在这个萃取器里面,会通过pointer_traits
获取一些分配器的类型属性。
4. 从指针萃取器角度谈traits技法
所谓traits
,字面意思是特性、特征,所以说白了,traits技法其实就是获取未知类型的某些属性,为什么说是未知,因为traits主要用于模板编程中,根据模板类型去获取某些类型特性,如果是已知的类型,那就没有必要使用traits技法了。
比如本篇文章所讲的pointer_traits
,它就是使用traits技法的典型案例,按照字面意思我们可以理解为指针的特性,所以非特化的pointer_traits
它就是用于获取某些类指针的类型特性,而一般特化的pointer_traits
其实是用于原生指针类型,比如int*
这样的。
下面我们再看一看怎么使用非特化的pointer_traits
获取类指针的特性,如下:
#include <memory> #include <iostream> #include <typeinfo> #include <cxxabi.h> const char* GetRealType(const char* p_szSingleType) { const char* szRealType = abi::__cxa_demangle(p_szSingleType, nullptr, nullptr, nullptr); return szRealType; } int main() { using type = typename std::pointer_traits<std::shared_ptr<int>>::element_type; const std::type_info &info = typeid(type); std::cout << GetRealType(info.name()) << std::endl; return 0; }
代码输出:int
,它获取了智能指针的element_type
特性。
到此这篇关于从c++标准库指针萃取器谈一下traits技法的文章就介绍到这了,更多相关c++ traits技法内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!
- 本文固定链接: https://zxbcw.cn/post/217388/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)