我的编程空间,编程开发者的网络收藏夹
学习永远不晚

深入浅析C++ traits技术

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

深入浅析C++ traits技术

前言

  traits,又被叫做特性萃取技术,说得简单点就是提取“被传进的对象”对应的返回类型,让同一个接口实现对应的功能。因为STL的算法和容器是分离的,两者通过迭代器链接。算法的实现并不知道自己被传进来什么。萃取器相当于在接口和实现之间加一层封装,来隐藏一些细节并协助调用合适的方法,这需要一些技巧(例如,偏特化)。最后附带一个小小的例子,应该能更好地理解 特性萃取。

  下面大部分来源于《STL源码剖析》,看原书能了解更多细节。

Traits编程技法

  让我们一点点抛出问题,然后一点点深入。

  1. 首先,在算法中运用迭代器时,很可能会用到其相应型别(迭代器所指之物的型别)。假设算法中有必要声明一个变量,以“迭代器所指对象的型别”为型别,该怎么办呢?

  解决方法是:利用function template的参数推导机制。


template <class I, class T>
void func_impl(I iter, T t) {
        T tmp; // 这里就是迭代器所指物的类型新建的对象
        // ... 功能实现
}

template <class I>
inline
void func(I iter) {
        func_impl(iter, *iter); // 传入iter和iter所指的值,class自动推导
}

int main() {
    int i;
    func(&i);
}

  这里已经可以看出封装的意思了,没有一层impl的封装的话,每次你都要显式地说明迭代器指向对象型别,才能新建tmp变量。加一层封装显得清爽很多。

  迭代器相应型别不只是“迭代器所指对象的型别”一种而已。根据经验,最常用的相应型别有五种,然而并非任何情况下任何一种都可以利用上述的template参数推导机制来取得。

  函数的“template参数推导机制”推导的只是参数,无法推导函数的返回值类型。万一需要推导函数的传回值,就无能为力了。

  2. 声明内嵌型别似乎是个好主意,这样我们就可以直接获取。


template <class T>
struct MyIter {
    typedef T value_type; // 内嵌型别声明
    // ...
};

template <class I>
typename I::value_type
func(I ite) {
    return *ite;
}

// ...
MyIter<int> ite(new int(8));
cout << func(ite);

  看起来不错,但是并不是所有迭代器都是class type,原生指针就不行!如果不是class type,就无法为它定义内嵌型别。

  这时候就需要 偏特化 出现。

  3. 偏特化就是在特化的基础上再加一点限制,但它还是特化的template。


 template <class I>
  struct iterator_traits {
      typedef typename I::value_type value_type;
  };
  
  template <class I>
  struct iterator_traits<T*> {
      typedef T value_type;
  };
 
 template <class I>12 typename iterator_traits<I>::value_type
 func(I ite) {
     return *ite;
 }

  func在调用 I 的时候,首先把 I 传到萃取器中,然后萃取器就匹配最适合的 value_type。(萃取器会先匹配最特别的版本)这样当你传进一个原生指针的时候,首先匹配的是带<T*>的偏特化版本,这样 value_type 就是 T,而不是没有事先声明的 I::value_type。这样返回值就可以使用 typename iterator_traits<I>::value_type 来知道返回类型。

  下面附上《STL源码剖析》的图片:

让traits干更多东西

  迭代器有常见有五种类型: value_type, difference_type, reference_type, pointer_type都比较容易在 traits 和 相应偏特化中提取。但是,iterator_category一般也有5个,这个相应型别会引发较大规模的写代码工程。

  例如,我们实现了 func_II, func_BI, func_RAI 分别代表迭代器类型是Input Iterator,Bidirectional Iterator和Random Access Iterator的对应实现。

  现在,当客端调用func()的时候,我们可能需要做一个判断:


template<class Iterator>
void func(Iterator& i) {
    if (is_random_access_iterator(i))
        func_RAI(i);
    if (is_bidirectional_iterator(i))
        func_BI(i);
    else
        func_II(i);
}

  但这样在执行时期才决定使用哪一个版本,会影响程序效率。最好能够在编译期就选择正确的版本。

  重载这个函数机制可以达成这个目标。


struct input_iterator_tag {};
 struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
 // ...
 // 继承的好处就是,当函数需要用 input_iterator_tag 的时候
// 假设你传进一个forward_iterator_tag,它会沿继承向上找,知道符合条件

  声明了一些列 tag 之后,我们就可以重载 func函数: func(tag)。

  到这里,各个型别的具体重载实现已经写好,但是需要一个统一的接口,这时候 traits 就可以出场了。


template<class Iterator>
 inline void func(Iterator& i)
 {
    typedef typename Iterator_traits<Iterator>::iterator_category category;
    __func(i, category()); // 各型别的重载
 }

简单实例代码

  所以说,traits一方面,在面对不同的输入类时,能找到合适的返回型别;另一方面,当型别对应有不同的实现函数的时候,能起到一个提取型别然后分流的作用。

  先假设我们有一个 func 函数,可以接受 自定义的类 或者 原始的指针 作为参数,并自动输出使用了什么tag。

  首先根据 traits(由本身或偏特化版本实现) ,它会提取 u 的返回型别,然后调用对应的构造函数 return_type(), 来当作各个重载版本 __func 的重载标志区分不同的实际函数。

  

首先我们看看接口代码的编写


 template <class unknown_class>
 inline typename unknown_class_traits<unknown_class>::return_type // 萃取器取得对应型别
 func(unknown_class u) {
     typedef typename unknown_class_traits<unknown_class>::return_type return_type;
     return __func(u, return_type()); // 需要调用构造函数当tag
 }

然后是实现设定的 tag ,用来模仿前面说的 II,RAI等


 template <class unknown_class>
 inline typename unknown_class_traits<unknown_class>::return_type
 return_type(unknown_class) {
     typedef typename unknown_class_traits<unknown_class>::return_type RT;
     return RT();
 }

有了这些我们就可以测试了


struct A {};
struct B : A{};

然后是 traits 隆重登场,有两个偏特化版本。



template <class unknown_class>
struct unknown_class_traits {
    typedef typename unknown_class::return_type return_type;
};


template <class T>
struct unknown_class_traits<T*> {
    typedef T return_type;
};


template <class T>
struct unknown_class_traits<const T*> {
    typedef const T return_type;
};

突然忘记了交代 unknown_class 的结构,自定义的类,必须要 typedef。


 template <class AorB>
struct unknown_class {
   typedef AorB return_type;
};

最后是func各个重载版本。


template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, A) {
    cout << "use A flag" << endl;
    return A();
}

template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, B) {
    cout << "use B flag" << endl;
    return B();
}

template <class unknown_class, class T>
T
__func(unknown_class, T) {
    cout << "use origin ptr" << endl;
    return T();
}

有了这些我们就可以测试了


int main() {
    unknown_class<B> b;
    unknown_class<A> a;
    //unknown_class<int> i;
    int value = 1;
    int *p = &value;

    A v1 = func(a);
    B v2 = func(b);
    int v3 = func(p);

    char ch = getchar();
}

  可以看到,对于用自定义类传入同一个接口,它会自动使用对应的函数,而且返回值也合适。对原始指针也适用,完美!

下面是完整代码:


#include <iostream>
using namespace std;


struct A {};
struct B : A{}; // 继承的好处就是,当函数需要参数为A,
                // 而你传入的参数为B的时候,可以往上一直找到适合的对象


template <class AorB>
struct unknown_class {
    typedef AorB return_type;
};


template <class unknown_class>
struct unknown_class_traits {
    typedef typename unknown_class::return_type return_type;
};


template <class T>
struct unknown_class_traits<T*> {
    typedef T return_type;
};


template <class T>
struct unknown_class_traits<const T*> {
    typedef const T return_type;
};



template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
return_type(unknown_class) {
    typedef typename unknown_class_traits<unknown_class>::return_type RT;
    return RT();
}

template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, A) {
    cout << "use A flag" << endl;
    return A();
}

template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
__func(unknown_class, B) {
    cout << "use B flag" << endl;
    return B();
}

template <class unknown_class, class T>
T
__func(unknown_class, T) {
    cout << "use origin ptr" << endl;
    return T();
}

template <class unknown_class>
inline typename unknown_class_traits<unknown_class>::return_type
func(unknown_class u) {
    typedef typename unknown_class_traits<unknown_class>::return_type return_type;
    return __func(u, return_type());
}

int main() {
    unknown_class<B> b;
    unknown_class<A> a;
    //unknown_class<int> i;
    int value = 1;
    int *p = &value;

    A v1 = func(a);
    B v2 = func(b);
    int v3 = func(p);

    char ch = getchar();
}

结束语

  特性提取花了自己好多时间,不过当程序跑出来的瞬间还是挺开心的。

  首先要感谢侯捷老师,老师的书讲得这么清楚,我还是笨笨的看得一知半解。

  看完这个可以看图像的傅里叶变换啦,啊哈哈~

以上就是C++ traits技术浅谈的详细内容,更多关于C++ traits技术的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

深入浅析C++ traits技术

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++中traits技术的示例分析

这篇文章主要介绍了C++中traits技术的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Traits编程技法  让我们一点点抛出问题,然后一点点深入。  1. 首先
2023-06-15

C++图形编程技术指南深入解析

c++++ 图形编程涉及使用 opengl 等 api 操作图形硬件。它包括 2d 图形(绘制形状、纹理映射)和 3d 图形(3d 坐标系、对象渲染、着色器)。本指南深入探讨了这些概念,并提供了一个构建简单 2d 游戏的实战案例。C++ 图
C++图形编程技术指南深入解析
2024-05-15

浅析MPLS技术

  传统的IP数据转发是基于逐跳式的,每个转发数据的路由器都要根据IP包头的目的地址查找路由表获得下一跳的出口,这是个繁琐又效率低下的工作,主要原因是两个:1、有些路由的查询必须对路由表进行多次查找,这就是所谓的递归搜索;2、由于路由匹配遵循最长匹配原则,所以迫使几乎所有的路由器的交换引擎必须用软件来实现,用软件实现的
浅析MPLS技术
2024-04-18

浅析QinQ技术

  QinQ技术(也称Stacked VLAN或者Double VLAN)是指用户私网VLAN标签封装在公网VLAN标签中,使报文带着两层VLAN标签穿越运营商的骨干网络,在公网中只根据外层VLAN标签传播,私网VLAN标签被屏蔽,这样,不仅对数据流进行了区分,而且由于私网VLAN标签被透明传送,不同的用户VLAN标签
浅析QinQ技术
2024-04-18

深入浅析AngularSSR

AngularUniversal主要关注将AngularApp如何进行服务端渲染和生成静态HTML,对于用户交互复杂的SPA并不推荐使用SSR,本文是在Angular14环境中完成,有些内容对于新的Angular版本可能并不适用,感兴趣的朋友一起通过本文学习
2022-11-13

深入浅析MySQL Explain

目录一、id二、select_type三、table四、type五、possible_keys六、Key七、key_len八、ref九、rows十、Extra在日常工作中,我们会有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些S
2022-07-08

深入剖析 C++ 递归:原理、实现和优化技术

递归是一种通过函数自调解决问题的编程技术,在 c++++ 中可通过调用自身并传递不同参数实现。优化技术包括尾递归优化、备忘录和剪枝。递归代码通常比迭代代码效率低,但当提供更简洁清晰的解决方案时,仍可能是更好的选择。深入剖析 C++ 递归:原
深入剖析 C++ 递归:原理、实现和优化技术
2024-05-02

深入浅析Android JSON解析

JSON语法 首先看JSON的语法和结构,这样我们才知道怎么去解析它。JSON语法时JavaScript对象表示语法的子集。 JSON的值可以是: 数字(整数或者浮点数) 字符串(在双引号内) 逻辑值(true 或 false) 数组(使用
2022-06-06

深入分析Android NFC技术 android nfc开发

从概念,实现原理以及最红实现的源码等有助于大家对NFC技术有更深入的理解。NFC 是 Near Field Communication 缩写,即近距离无线通讯技术。可以在移动设备、消费类电子产品、PC 和智能控件工具间进行近距离无线通信。简
2023-05-30

Java与Go:深入剖析技术优劣势

java 和 go 的优劣势:语言特性:两者都是强类型、面向对象语言,但 go 的并发支持更出色。性能:go 的 goroutine 带来并发性能优势,而 java 的垃圾回收可能带来开销。适用性:java 适用于大型企业级应用,而 go
Java与Go:深入剖析技术优劣势
2024-04-08

Node.js Redis vs. Node.js:深入剖析缓存技术

缓存是提升应用程序性能的关键技术,在 Node.js 开发中,Redis 和 Node.js 内置缓存机制是两个常用的选择。RedisRedis 是一个开源的、内存中的数据结构存储,专为缓存和消息传递而设计。它提供多种数据结构,包括字符串、列表、哈希和集合,并支持原子操作和数据持久化。优势:高性能:Redis 是一个高
Node.js Redis vs. Node.js:深入剖析缓存技术
2024-04-03

深入浅析JAVA的继承

本篇文章给大家分享的是有关深入浅析JAVA的继承,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。继承(inheritance)是Java OOP中一个非常重要的概念。继承是在复用
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录