模板特化与类型萃取(2)

一、类型萃取的目的和意义

1、类型萃取是用途

(1)典型应用就是:在模板函数中区分T是 源生类型POD 还是 自定义类型

(2)POD,Plain Old Data,简单理解就是C++从C继承而来的基本类型,如int、double等

(3)POD类型的本质是没有C++叠加的那些高级特征(构造析构,拷贝构造,移动语义,虚函数等)

2、为什么要区分POD类型和非POD类型

(1)典型案例就是copy时,POD类型直接memcpy(C语言的一个库函数)即可,而非POD类型需要用for循环结合“=”(默认提供的运算符重载后的=可以实现对象拷贝),挨个对象拷贝

(2)非POD类型不能memcpy(涉及到了动态内存),本质是因为需要深拷贝以避免出错

(3)代码演练:int数组和string数组的复制对比

#include <iostream>
#include <cstring>//c语言的那个string.h头文件
#include <string> //C++的string头文件,与上边的那个不同

using namespace std;

template <typename T>
void mycopy(T* Dst, T* Src, int count)
{
    memcpy(Dst, Src, count);
}

int main(int argc, char *argv[])
{
#if 0    
    int a[3] = {1, 2, 3}, b[3] = {0};
#else

    string a[3] = {"linux", "android", "harmonyos"};
    string b[3];

    
    for(int i = 0; i < 3; i++)
    {
        cout << a[i] << ' ';
    }
    cout << endl;
 
    mycopy<string>(b, a, sizeof(string)*3);//虽然可以执行,但会报错,这就是浅拷贝
    a[1] = "ubuntu";                       //报错信息:free(): invalid size ,已放弃 (核心已转储)
 
    //mycopy<int>(b, a, sizeof(int)*3);
    //a[0] = 0;

    for(int j = 0; j < 3; j++)
    {
        cout << a[j] << ' ';
    }
    cout << endl;

    for(int k = 0; k < 3; k++)
    {
        cout << b[k] << ' ';
    }
    cout << endl;

#endif
    return 0;
}

二、类型萃取实战演练

1、使用is_pod解决上面的问题

(1)std::is_pod介绍
在这里插入图片描述
在这里插入图片描述

//使用示例
#include <iostream>
#include <type_traits>
 
struct A {
    int m;
};
 
struct B {
    int m1;
private:
    int m2;
};
 
struct C {
    virtual void foo();
};
 
int main()
{
    std::cout << std::boolalpha;
    std::cout << std::is_pod<A>::value << '\n';
    std::cout << std::is_pod<B>::value << '\n';
    std::cout << std::is_pod<C>::value << '\n';
}

输出:
true
false
false

(2)代码实战,使用is_pod来完善mycopy解决上节中的问题

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

template <typename T>
void mycopy(T* Dst, T* Src, int count)
{
    if (is_pod<T>::value)
    {
        memcpy(Dst, Src, sizeof(T)*count);
    }
    else
    {
        for(auto i = 0; i < count; i++)
        {
            Dst[i] = Src[i];
        }
    }
}

int main(int argc, char *argv[])
{
    string a[3] = {"linux", "android", "harmonyos"};
    string b[3];

    for(int i = 0; i < 3; i++)
    {
        cout << a[i] << ' ';
    }
    cout << endl;
 
    mycopy<string>(b, a, 3);
    a[1] = "ubuntu";

    for(int j = 0; j < 3; j++)
    {
        cout << a[j] << ' ';
    }
    cout << endl;

    for(int k = 0; k < 3; k++)
    {
        cout << b[k] << ' ';
    }
    cout << endl;

    return 0;
}

2、std提供的其他类型萃取

简旧数据类型 (PODType) ,即既是平凡又是标准布局:
在这里插入图片描述

三、类型萃取是如何实现的

1、一种可能的实现(方法一)

(1)把所有pod类型组成列表,在内部挨个判断,借助两个关键字:

typeid用来返回一个变量(表达式)(对象)的类型,类似于C语言中的:typeof() 
是GUN C提供的一种特性,它可以取得变量的类型,或者表达式的类型。

(2)优点:可以实现,且能实现<type_traits>中所有的标准库萃取工具

(3)缺点:运行时判断,占用运行时资源,效率低

2、使用类模板的特化实现(方法二)

(1)代码实践演示

#include <iostream>
#include <cstring>
#include <string>

using namespace std;


// 泛化版本的my_is_pod
template<typename T>
struct my_is_pod
{
	static bool value;
};

template<typename T>
bool my_is_pod<T>::value = false;


// int类型的特化版本
template<>
struct my_is_pod<int>
{
	static bool value;
};
bool my_is_pod<int>::value = true;

// double类型的特化版本
template<>
struct my_is_pod<double>
{
	static bool value;
};
bool my_is_pod<double>::value = true;

// short类型的特化版本
template<>
struct my_is_pod<short>
{
	static bool value;
};
bool my_is_pod<short>::value = true;

//string类型使用这样的方法还会报错,我试过,但没有去深入研究

template<typename T>
void mycopy(T *dest, const T *src, int cnt)
{
	// 我们需要一种技术,在这里可以去区分,T到底是pod还是非pod
	if (my_is_pod<T>::value)
	{
		cout << "if" << endl;
		memcpy(dest, src, cnt*sizeof(T));
	}
	else
	{
		cout << "else" << endl;
		for (int i=0; i<cnt; i++)
		{
			dest[i] = src[i];			// 非pod类型使用operator=是可以复制的
		}
	}
}


int main(void)
{
//	cout << boolalpha << my_is_pod<double>::value << endl;

	short a[3] = {4, 5, 6};
	short b[3] = {0};
	
	mycopy<short>(b, a, 3);
	for(int i=0; i<3; i++)
		cout << b[i] << "   ";
	cout << endl;

/*	
	string s1[3] = {"linux", "android", "harmonyos"};
	string s2[3];
	
	mycopy<string>(s2, s1, 3);			// 非pod类型不能memcpy,因为会浅拷贝导致错误

	for (int i=0; i<3; i++)
	{
		cout << "s2[" << i << "] = " << s2[i] << endl;
	}
	
	s1[0] = "abc";
	cout << "last, s2[0] = " << s2[0] << endl;
*/	
	
	return 0;
}

(2)总结:特化实现萃取的关键,就是特化版本的优先级高于泛化版本

(3)思考:使用类模板特化,是否会增加代码量,影响程序效率?
  会增加代码量,不会影响效率,模板特化是在编译时判断的。

四、类型萃取的另一种可能实现

1、不使用静态成员变量
2、改为使用成员函数
3、使用typedef增加一层中间层
(1)class/struct内使用typedef定义子类型的方法
(2)增加名为value_type的子类型中间层,实现pod判断

#include <iostream>
#include <cstring>
#include <string>

using namespace std;

struct FalseType
{ 
	bool GetType()
	{
		return false;
	}	
};

struct TrueType
{ 
	bool GetType()
	{
		return true;
	}	
};

// 泛化版本的my_is_pod
template<typename T>
struct my_is_pod
{
    typedef FalseType value_type;
};


template <>
struct my_is_pod<short>
{
    typedef TrueType value_type;
};

int main(void)
{
    cout << boolalpha << my_is_pod<int>::value_type().GetType() << endl;//value_type()的这个()表示构造函数
	return 0;
}

五、迭代器萃取与泛型算法

1、STL的核心

(1)STL,就是C++提供的一套标准实现的template化的library

(2)STL有很多内容,但是核心就是2个:泛型容器、泛型算法

(3)为了实现泛型容器,引入了迭代器,迭代器是指针的泛化抽象

(4)泛型算法可以接受多种容器,每种容器内可以存储多种数据载体,这就是泛型算法的2级泛化支持

2、泛型算法实现的难题和解法

(1)问题1:泛型算法无法预知自己处理的是什么容器

解决思路:将容器降级为迭代器来对接泛型算法。所以任何容器都必须内置一个迭代器

(3)问题2:泛型算法无法预知容器内存储的元素类型,是否POD

解决思路:提供迭代器萃取器,在泛型算法内预先萃取并使用容器元素类型

六、迭代器萃取器的设计解读

(1)迭代器萃取器本质是一个类,叫iterator_traits,属于辅助迭代器的第三方类

(2)解读参考:https://blog.csdn.net/virtual_func/article/details/48398451

七、迭代器萃取器的特化

1、萃取器的特化讲解

(1)参考:https://blog.csdn.net/terence1212/article/details/52287762

(2)总结:本质是偏特化结合类型萃取技术

2、本章节文章总结

(1)主要讲了2项技术,一个 是特化,一个是萃取

(2)特化的核心价值是,让模板类/函数按一定优先级规则去匹配

(3)萃取的核心价值是,让我们在写泛型算法时可以预先得知未来传参容器及容器内元素的型别特征

(4)如果只是使用STL,实际上不需要关注特化和萃取

(5)真正理解模板技术、特化、萃取等技术的使用和实现,你才会感受到C++的魅力,知道C++为什么效率高

(6)从实用角度讲,不需要真的深度去研究这些。但是如果完全不懂甚至不知道这些技术的存在,那休想用好C++

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值