从c++标准库指针萃取器谈一下traits技法(推荐)

本篇文章基于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&lt;_Args...&gt;是一个有效的类型,那这个类型就是_Op&lt;_Args...&gt;,否则就是_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&lt;_Up&gt;这个类型存在则它就是_ptr::rebind&lt;_Up&gt;,否则根据类型模板__replace_first_arg的实现,若_ptr是模板特化_Template&lt;_Tp, _Types...&gt;,则它是_Template&lt;_Tp, _Types...&gt;,否则就没有类型;
  • 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&lt;typename _Up&gt; 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技法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入浅析C++ traits技术

    前言 traits,又被叫做特性萃取技术,说得简单点就是提取"被传进的对象"对应的返回类型,让同一个接口实现对应的功能.因为STL的算法和容器是分离的,两者通过迭代器链接.算法的实现并不知道自己被传进来什么.萃取器相当于在接口和实现之间加一层封装,来隐藏一些细节并协助调用合适的方法,这需要一些技巧(例如,偏特化).最后附带一个小小的例子,应该能更好地理解 特性萃取. 下面大部分来源于<STL源码剖析>,看原书能了解更多细节. Traits编程技法 让我们一点点抛出问题,然后

  • 从c++标准库指针萃取器谈一下traits技法(推荐)

    本篇文章基于gcc中标准库源码剖析一下标准库中的模板类pointer_traits,并且以此为例理解一下traits技法. 说明一下,我用的是gcc7.1.0编译器,标准库源代码也是这个版本的. 还是先看一下思维导图,如下: 1. 指针萃取器pointer_traits说明 首先说明一下哈,官方并没有指针萃取器这个名称,其实pointer_traits是类模板,它是c++11以后引入的,可以通过传入的重绑定模板类型得到相应的指针类型,比较官方的描述是:pointer_traits 类模板提供标准

  • C++实现STL迭代器萃取的示例代码

    目录 导言 什么是迭代器 为什么需要迭代器萃取 value type difference type reference type point type iterator_category 知识点补充 例外 导言 什么是迭代器 迭代器是一种抽象的设计概念,<Design Patterns>一书中对于 iterator 模式的定义如下:提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表述方式. 为什么需要迭代器萃取 有时在我们使用迭代器的时候,很可能会用

  • 深入解析Go语言的io.ioutil标准库使用

    今天我们讲解的是golang标准库里边的io/ioutil包–也就是package io/ioutil 1.ioutil.ReadDir(dirname string)这个函数的原型是这样的 func ReadDir(dirname string) ([]os.FileInfo, error) 不难看出输入的是dirname类型是string类型的 譬如"d:/go",然会是一个FileInfo的切片,其中FileInfo的结构是这样的 复制代码 代码如下: type FileInfo

  • Python3标准库之functools管理函数的工具详解

    1. functools管理函数的工具 functools模块提供了一些工具来调整或扩展函数和其他callable对象,从而不必完全重写. 1.1 修饰符 functools模块提供的主要工具就是partial类,可以用来"包装"一个有默认参数的callable对象.得到的对象本身就是callable,可以把它看作是原来的函数.它与原函数的参数完全相同,调用时还可以提供额外的位置或命名函数.可以使用partial而不是lambda为函数提供默认参数,有些参数可以不指定. 1.1.1 部

  • Python3标准库之threading进程中管理并发操作方法

    1. threading进程中管理并发操作 threading模块提供了管理多个线程执行的API,允许程序在同一个进程空间并发的运行多个操作. 1.1 Thread对象 要使用Thread,最简单的方法就是用一个目标函数实例化一个Thread对象,并调用start()让它开始工作. import threading def worker(): """thread worker function""" print('Worker') threads

  • Golang标准库unsafe源码解读

    目录 引言 unsafe包 unsafe构成 type ArbitraryType int type Pointer *ArbitraryType 灵活转换 潜在的危险性 正确的使用姿势 错误的使用姿势 func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr 引言 当你阅读Golang源码时一定遇到过unsafe.Pointe

  • C++ STL标准库std::vector扩容时进行深复制原因详解

    目录 引子 查找原因 解决方法 结论 引子 但是笔者却发现了一个奇怪的现象,std::vector扩容时,对其中的元素竟然进行的是深复制.请看示例代码: #include <iostream> #include <vector> struct Test { Test() {std::cout << "Test" << std::endl;} ~Test() {std::cout << "~Test" <

  • GoLang之标准库encoding/json包

    目录 1.JSON介绍 2.JSON序列化.反序列化介绍 3.encoding/json包介绍 4.Marshal函数 5.Umarshal函数 6.结构体标签Tag 注:本文以Windos系统上Go SDK v1.8进行讲解 1.JSON介绍 在进行前后分离式开发时,json显得格外的重要,因为他是链接前后台重要的枢纽json是储存和交换文本信息的语法,他类似于xml,但是他比xml更加的便捷,快速,易于解析.主要使用场景就是作为前后台数据交互的枢纽,以下是一个简单json的格式:JSON:

  • C++标准库bitset类型的简单使用方法介绍

    std::bitset是STL的一部分,准确地说,std::bitset是一个模板类,它的模板参数不是类型,而整形的数值(这一特性是ISO C++2003的新特性),有了它我们可以像使用数组一样使用位. #include<bister> using std::bitset; 一句话定义:可自定义位数,用作记录二进制的数据类型. 一,定义和初始化 bitset<n> b;                           //b有n位,每位都为0; bitset<n>

  • Python标准库之随机数 (math包、random包)介绍

    我们已经在Python运算中看到Python最基本的数学运算功能.此外,math包补充了更多的函数.当然,如果想要更加高级的数学功能,可以考虑选择标准库之外的numpy和scipy项目,它们不但支持数组和矩阵运算,还有丰富的数学和物理方程可供使用. 此外,random包可以用来生成随机数.随机数不仅可以用于数学用途,还经常被嵌入到算法中,用以提高算法效率,并提高程序的安全性. math包 math包主要处理数学相关的运算.math包定义了两个常数: 复制代码 代码如下: math.e   # 自

随机推荐