【C++】模板进阶,搞定泛型编程

一、模板的概念

模板概念:

  • 模板实现了通用类型的代码,不需要关心数据的类型,因此 C++标准模板库STL产生。模板可以分为:类模板和函数模板,其类型推导和具体代码生成都是在编译阶段。
    • 没有实例化的模板:编译器只是对模板进行简单的语法检测,并不会生成代码
    • 实例化之后:根据用户对模板实例化的类型来生成代码,并且对生成的代码进行语法检测

模板类型的参数:

  • 类型模板参数:类型不确定的,实例化后才生成对应代码
  • 非类型模板参数:类型已经具体化
    • 如果有缺省或者传参时,必须是常数,因为要在编译阶段确定大小
    • 浮点数、类对象以及字符串不允许作为非类型模板参数
template <class T, size_t N = 5>	// 非类型模板参数缺省时,必须是常数
class arr
{
    
    
public:
	arr()
		:_size(0)
	{
    
    }
	T& operator[](const int index){
    
    return _arr[index];}
	void insert(const T& data){
    
    _arr[_size++] = data;}
private:
	T _arr[N];
	size_t _size;
};
int main()
{
    
    
	arr<int, 10> a;	// 非类型模板参数传参时必须是常数,否则报错
	return 0;
}

模板的编译链接过程:

  • 有以下加法模板函数:函数声明:a.h头文件中,函数定义:a.cpp中
a.h头文件:加法模板的声明
#pragma once
template <class T>
T Add(const T& left, const T& right);

a.cpp源文件:加法模板的定义
#include "a.h"
template <class T>
T Add(const T& left, const T& right)
{
    
    
	return left + right;
}

main.cpp 
#include "a.h"
int main()
{
    
    
	int sum = Add(1, 2);
	return 0;
}

注意:这段代码在链接过程中会发生错误,找不到 int 类型的模板函数地址!

  • 原因:模板的分离编译,没有生成对应类型的代码,造成链接时找不到函数地址入口
    • 编译:对程序进行语法正误检测,检查无误后生成汇编代码。注意:头文件不参加编译,并且编译器对一个工程中的多个源文件是分离编译的。
    • 链接:将对个 .obj 目标文件合并成一个,并处理未解决符号表中的地址问题,到其他源文件的导出符号表中寻找对应函数地址。
  • 分离编译:同一项目有多个源文件共同实现,而每个源文件都是单独生成目标文件,最后才将所有文件链接起来形成单一的可执行文件。
    • a.cpp源文件在编译时,编译器没有看到对 Add 模板函数的实例化,因此不会生成具体的 int 类型代码
    • main.obj 目标文件在连接时,去其他目标文件找 Add< int >的函数地址,没有找到因此报连接错误。

解决:

  1. 在模板定义处显示实例化,不推荐
  2. 将模板声明和定义放到同一 .hpp 头文件中
1.在模板定义处显示实例化
#include "a.h"
template <class T>
T Add(const T& left, const T& right)
{
    
    
	return left + right;
}
void Test()
{
    
    
	Add<int>(1, 2);
	Add<double>(1.2, 2.3);
}

2.放在同一头文件中
#pragma once
template <class T>
T Add(const T& left, const T& right);

template <class T>
T Add(const T& left, const T& right)
{
    
    
	return left + right;
}

二、模板的特化

函数模板特化:

  • 下面实现一个通用类型的比较函数,返回两者较大数。
template <class T>
T& Max(T& l, T& r)
{
    
    
	return l > r ? l : r;
}
int main()
{
    
    
	int a = 1, b = 2;
	cout << Max(a, b) << endl;
	return 0;
}
  • 对于int、double等类型能正确执行,但是如果传入字符串类型,比较的是地址大小,和预期不同,因此采用函数模板特化方式处理特殊类型

解决:

  1. 直接将特殊类型的函数重载出来,较为简单,推荐使用
  2. 进行模板函数特化处理
template <class T>
T& Max(T& l, T& r)
{
    
    
	return l > r ? l : r;
}
// 进行函数重载
const char* Max(const char* l, const char* r)
{
    
    
	if (strcmp(l, r) == 1)
		return l;
	else
		return r;
}
// 进行特化处理:重载参数存在 & 的函数模板
template <>
const char*& Max<const char*>(const char*& l, const char*& r)
{
    
    
	if (strcmp(l, r) > 0)
		return l;
	else
		return r;
}
int main()
{
    
    
	int a = 1, b = 2;
	cout << Max(a, b) << endl;
	const char* ptr1 = "bsd";
	const char* ptr2 = "acv";
	cout << Max(ptr1, ptr2) << endl;
	return 0;
}

函数模板特化方法:

  1. 关键字 template 后加一个空的尖括号<>
  2. 函数名后加尖括号,尖括号里指针特化的类型
  3. 函数形参列表:和模板函数的基础参数类型相同

类模板特化:

  • 类模板特化分为:全特化和偏特化
    • 全特化:将模板参数列表中所有的参数都确定化
    • 偏特化:针对默认参数进一步进行条件限制,形成特化版本
#include <iostream>
using namespace std;
template <class T1, class T2>
class Date
{
    
    
public:
	Date() {
    
     cout << "Date<T1, T2>" << endl; }
	T1 _day;
	T2 _week;
};

// 全特化版本
template <>
class Date <int, char>
{
    
    
public:
	Date() {
    
     cout << "Date<int, char>" << endl; }
	int _day;
	char _week;
};

// 偏特化:1.模板部分参数特化
template <class T1>
class Date <T1, char>
{
    
    
public:
	Date() {
    
     cout << "Date<T1, char>" << endl; }
	T1 _day;
	char _week;
};

// 偏特化:2.对参数进行进一步限制
template <classT1, class T2>
class Date <T1*, T2*>
{
    
    
public:
	Date() {
    
     cout << "Date<T1*, T2*>" << endl; }
	T1 _day;
	T2 _week;
};
int main()
{
    
    
	Date<int, char> d1;		// 走全特化版本
	Date<int, int> d2;		// 走基础模板
	Date<char, char> d3;	// 走特化char版本
	Date<int*, int*>d4;		// 走特化指针版本
	
	return 0;
}

特化方法:

  • 在关键字 template 后的尖括号中进行处理,全特化:空的,偏特化:添加不需要特化的,对参数限制:添加限制参数
  • 类名后加尖括号和特化内容

三、类型萃取

  • 类模板的特化应用:类型萃取

下面实现一个通用类型的拷贝函数:

template <class T>
void Copy(T& dst, const T& src, size_t size)
{
    
    
	// 内存拷贝:优点:效率高  缺点:设计内存资源管理是浅拷贝
	memcpy(dst, src, size * sizeof(T));

	// 深拷贝:优点:一定不会出错  缺点:效率太低
	for (int i = 0; i < size; i++)
		dst[i] = src[i];
}
  • 采用 memcpy 内存拷贝:拷贝效率较高,对于内置类型不会出错。但是对于设计动态内存的数据,则会造成内存泄漏,甚至因为 double free 造成程序崩溃
  • 采用深拷贝的方式:拷贝效率太低,但是深拷贝不会造成错误

因此使用类模板特化:类型萃取,将内置类型提取出来,进行内存拷贝

// 类型萃取
struct True_Type
{
    
    
	static bool Get(){
    
    return true;}
};
struct False_Type
{
    
    
	static bool Get(){
    
    return false;}
};
// 其它类型
template <class T>
struct TypeTraits
{
    
    
	typedef False_Type PODTYPE;
};
// 内置类型:特化处理
template<>
struct TypeTraits<int>
{
    
    
	typedef True_Type PODTYPE;
};
template<>
struct TypeTraits<char>
{
    
    
	typedef True_Type PODTYPE;
};
template<>
struct TypeTraits<double>
{
    
    
	typedef True_Type PODTYPE;
};
template<>
struct TypeTraits<short>
{
    
    
	typedef True_Type PODTYPE;
};

// 对拷贝函数进行重载
template <class T>
void Copy(T* dst, const T* src, size_t size, True_Type)
{
    
    
	// 内置类型
	memcpy(dst, src, size * sizeof(T));
}
template <class T>
void Copy(T* dst, const T* src, size_t size, False_Type)
{
    
    
	// 其他类型
	for (size_t i = 0; i < size; i++)
		dst[i] = src[i];
}
template <class T>
void Copy(T* dst, const T* src, size_t size)
{
    
    
	Copy(dst, src, size, TypeTraits<T>::PODTYPE());
}
int main()
{
    
    
	int arr1[] = {
    
     1,2,3,4 };
	int arr2[4] = {
    
     0 };
	Copy(arr2, arr1, 4);

	String s[] = {
    
     "111", "222", "333" };
	String p[3];
	Copy(p, s, 3);
	return 0;
}

String类

class String
{
    
    
public:
	// 构造
	String(const char* str = "")
	{
    
    
		if (str == nullptr)
			str = "";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	// 拷贝构造
	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
    
    
		strcpy(_str, s._str);
	}
	// 析构
	~String()
	{
    
    
		if (_str)
		{
    
    
			delete[] _str;
			_str = nullptr;
		}
	}
	// 赋值运算符重载:现代法
	String& operator=(String s)
	{
    
    
		swap(_str, s._str);
		return *this;
	}
private:
	char* _str;
};

猜你喜欢

转载自blog.csdn.net/qq_45691748/article/details/114294119