C++ 泛型编程


title: C++ 泛型编程
date: 2020-06-16 23:12:48
tags:
- C++
categories: C++

C++ 泛型编程

更好的阅读体验

概述

函数模板

基本范例

范例 1.0

#include <iostream>

using namespace std;

template<typename T>
void func(T a, T b)
{
	cout << "template" << endl;
	cout << a << endl;
	cout << "a: " << typeid(a).name() << endl;
	cout << "b: " << typeid(b).name() << endl;
}

int main()
{
	func(1, 2);
	func<int>(2, 3);
	func<int>(2, 3.2);

	return 0;
}

基本写法,在函数开头写template< ... >, 在尖括号里面写 typename T,这里的T是一个符号,表示类型T。

typename可以用class代替。写成下面这样。我查到的这只是一个历史版本。

template<class T>
void func(T a, T b)
{
	cout << "template" << endl;
	cout << a << endl;
	cout << "a: " << typeid(a).name() << endl;
	cout << "b: " << typeid(b).name() << endl;
}

调用的时候

	func(1, 2);
	func<int>(2, 3);
	func<int>(2, 3.2);	// func(2, 3.2); 不写<>错误。编译器编译时不做类型装换

必须符合编译器能在【编译时】推断出类型T是什么,如果能推断出来,尖括号的类型就可以不写

	func<>(2.2, 3.2);

也就是可以写空的尖括号或者压根就不写。

每当你用某个类型填写模板的时候。比如上面的func(1, 2)。编译器会用int填写模板,生成一套代码。在编译时就完成代码的“赋值”出多个类型版本。

范例 2.0

在需要多个参数的时候,可以写成这样,类型T 加 类型 U 结果是类型 V

template<typename T, typename U, typename V>
V add(T a, U b)
{
	return a + b;
}

调用。

	cout << add<int, double, int>(1, 1.2) << endl;
	cout << add<int, double, double>(1, 1.2) << endl;

遵循一个原则。模板写法还挺多情况的,某些值可以不写,但是一个原则。编译器必须在运行时推断出类型是什么。比如下面这样, 都是编译错误

	// 编译错误,编译时不知道 a + b 是什么类型。加一下算出结果才知道,这是运行时的事情
	cout << add<int, double>(1, 1.2) << endl;
	cout << add<>(1, 1.2) << endl;
	cout << add(1, 1.2) << endl;

函数模板重载

template<typename T>
void func(T * t)
{
	cout << "void func(T * t)" << endl;
	cout << typeid(t).name() << endl;
}

template<typename T>
void func(T t)
{
	cout << "void func(T t)" << endl;
	cout << typeid(t).name() << endl;
}

void func(int a)
{
	cout << "func(int a)" << endl;
}

调用

	int a = 1;
	int *pa = &a;

	func(a);
	func(static_cast<double>(a));
	func(static_cast<float>(a));
	func(pa);

输出

func(int a)
void func(T t)
double
void func(T t)
float
void func(T * t)
int *

函数模板也会在编译时,根据参数数量或者参数类型上不同,找自己最合适的版本去调用。参考上面的指针版本和非指针版本。

如果有普通函数和模板函数同名的时候,会优先调用普通函数。但是我认为这是编译器觉得的,尽量还是不要写出这种代码。

泛化,特化,全特化,偏特化,

泛化

大众化的,常规的。常规情况下,写的函数模板都是泛化的函数模板。大众化的,常规的。常规情况下,写的函数模板都是泛化的函数模板。

template<typename T, typename U>
void func(T &a, T &b)
{
	cout << "泛化" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

调用

	int a = 1;
	double b = 1.0;
	string ss = "123";
	func<int, double>(a, b);
	func<int, string>(a, ss);

特化

代表着从泛化版本中抽出来的一组子集

全特化

// 泛化版本
template<typename T, typename U>
void func(T &a, T &b)
{
	cout << "泛化" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

// 全特化版本
template<>
void func<int, double>(int &a, double &b)
{
	cout << "全特化" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

// 函数重载,这是一个普通函数
void func(int &a, double &b)
{
	cout << "普通函数" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

就是把泛化中的所有模板typename全部都给上具体类型。

编译器考虑的顺序:优先选择普通函数,然后才会考虑函数模板的特化版本,最后才会考虑函数模板的泛化版本

上面特化版本,直接直到了所有模板的类型。

意义是,在模板中为某些类型单独写一套代码。

可能会用到的场景。比如哈希函数。为某个类型的函数单独编写计算过程。

偏特化

一个是模板参数数量上的偏特化,一个是模板参数范围上的偏特化。

我们在全特化的基础上,试想,能不能给局部的类型,预定义参数,剩下的不给呢。

比如

// 全特化版本
template<>
void func<int>(int &a, double &b)	// void func<int, double>(int &a, double &b)
{
	...
}

结果是都不行的。函数模板不支持参数数量上的偏特化。要么全空着,泛化。要么全给,全特化。

另一个是参数范围上的偏特化。

int -> const int 类型变小

T->T*, T->T&, T->T&&。类型范围上变小了。

实际上,对于函数模板来讲,也不存在模板参数范围上的偏特化。因为这种所谓模板参数范围上的偏特化,实际上是函数模板的重载

template<typename T, typename U>
void func(T a, T b)
{
	cout << "void func(T a, T b)" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

template<typename T, typename U>
void func(T &a, T &b)
{
	cout << "void func(T &a, T &b)" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

感觉函数上泛化,特化的概念不是很深,具体应该要看类模板了。。。

各种奇奇怪怪的写法

template<typename, typename>
void func(int &a, int &b)
{
	cout << "泛化" << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

如果typename T的T后面没用到,可以不写(你不用你typename啥呢 ***)

非类型模板参数

模板参数还可以是 “非类型 模板参数(普通的参数),++17开始,支持非类型模板参数为auto类型。

并不是任何类型的参数都可以作为非类型模板参数。int类型可以,但double,float或者类类型string等等可能就不可以,不过double *这种指针类型可以。

一般允许做非类型模板参数的类型如下:可能不断增加

  • 整型或者枚举型

  • 指针类型

  • 左值 引用类型

  • auto或者decltype(auto)

  • 其他

案例一:

typedef void(*FuncType)(int, int);

void add1(int a, int b)
{
	cout << "add1:" << a << b << endl;
}

void add2(int a, int b)
{
	cout << "add2:" << a << b << endl;
}

template<typename T1, typename T2, typename Func = FuncType>
void test(T1 a, T2 b, Func func = add1)
{
	func(a, b);
}

调用

	test(1, 2);
	test(1, 3, add2);

模板中的类型可以有默认参数,在这个例子中Func默认是一个函数指针类型。并且参数默认值是一个函数指针。我们可以在调用时给其更换指针。

当然我们不让他是函数指针呢?

template<typename T1, typename T2, typename Func = FuncType>
void test(T1 a, T2 b, Func func = add1)
{
	//func(a, b);
}

test<int, int, int>(1, 2, 3);

也可以正常调用。

案例二:

默认类型还可以写int, double之类的东西。

template<typename T1 = int, typename T2 = double>
void test(T1 a, T2 b)
{
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
}

test(1, 2);
test(1, 2.2);

在这个例子中不写 =int他也能推断出来。

怎么这么多花里胡哨的语法。。。

案例三:

模板的类型也可以是某些值

template<typename T1 = int, typename T2 = double, int ConstValue = 100>
void test(T1 a, T2 b)
{
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << ConstValue << endl;
}

test<int, int, 200>(1, 2.2);

只是又多了一个可以写预定义常量的地方了。。。

上面的预定义值,还有可能的类似表现形式。

typename int ConstValue = 100
int ConstValue = 100
auto ConstValue = 100

类模板

类模板基本写法及其案例

template <typename T>
class Vector
{
public:
	typedef T* Iterator;
public:
	Vector();

	Vector(int size) 
	{
		this->m_size = size;
		this->m_data = new T[m_size];
	}

	~Vector()
	{
		delete[] m_data;
	}

	/**
		Vector& operator=(const Vector& others) 
		{
			// ...
		} 
		这里有没<T> 两种写法其实都可以
	*/
	Vector<T>& operator=(const Vector& others)
	{
		if (this == &others)
		{
			return;
		}
		if (this->m_data)
		{
			delete[] m_data;
		}
		this->m_size = others->m_size;
		this->m_data = new T[this->m_size];

		for (int i = 0; i < this->m_size; i++)
		{
			this->m_data[i] = others[i];
		}
	}

	T& operator [] (int index)
	{
		return this->m_data[index];
	}
public:

	void func1()
	{
		cout << "func1 called" << endl;
	}

	void func2();

	static void func_static()
	{
		cout << "func static called" << endl;
	}
private:
	T *m_data;
	int m_size;
};

template <typename T>
Vector<T>::Vector()
{
	this->m_data = nullptr;
	this->m_size = 0;
	cout << "hello world" << endl;
}

int main()
{
	Vector<int> vec(10);


	return 0;
}

推断指南

template <typename T>
struct Demo
{
	Demo()
	{

	}

	Demo(T a)
	{
		this->a = a;
	}
	T a;
};
template <typename T>
Demo(T)->Demo<T>;

调用

	Demo<int> d;
	Demo q(1);

上面 q(1)没有指定<>。但是却通过

template <typename T>
Demo(T)->Demo<T>;

这种语法推断出了类型。叫推断指南。在c++17才支持。只能用于构造部分。

写法

左侧部分内容或者形式时,请推断成->右侧的类型。右侧类型也被称为指南类型

左侧部分:该推断指南所对应的构造函数的函数声明,多个参数之间用,分隔。

右侧部分:类模板名,接着一个尖括号,尖括号中是模板参数名。

推断指南的存在意义就是让编译器 能够把模板参数的类型推断出来

另外

也可以

Demo q{2}; // 注意这里是 大括号,间接调用构造函数。最后推断指南确定了类型

泛化,特化,全特化,偏特化

泛化

一般普通写的模板都是泛化版本

下面的例子包含了类内定义函数和类外定义函数的写法示例

注意:类外定义模板函数内容的时候,要和声明在同文件,否则编译可能找不到

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "泛化版本: construct()" << endl;
		this->m_t = t;
		this->m_u = t;
	}

	void func()
	{
		cout << "泛化版本: func()" << endl;
		cout << typeid(this->m_t).name() << endl;
		cout << typeid(this->m_u).name() << endl;
	}

	void func1();

private:
	T m_t;
	U m_u;
};

template <typename T, typename U>
void TemplateClass<T, U>::func1()
{
	cout << "类外定义函数" << endl;
}

测试

	TemplateClass<int, double> tc(1, 2.2);
	tc.func();
	tc.func1();

输出

泛化版本: construct()
泛化版本: func()
int
double
类外定义函数

特化

类模板全特化

template <>然后下一行类名后面尖括号吧所有类型都写上,叫全特化

template <>
class TemplateClass<int, int>

然后也提供了类外定义函数的写法。

#include <iostream>

using namespace std;

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "泛化版本: construct()" << endl;
		this->m_t = t;
		this->m_u = t;
	}

	void func()
	{
		cout << "泛化版本: func()" << endl;
		cout << typeid(this->m_t).name() << endl;
		cout << typeid(this->m_u).name() << endl;
	}

	void func1();

private:
	T m_t;
	U m_u;
};

template <typename T, typename U>
void TemplateClass<T, U>::func1()
{
	cout << "类外定义函数" << endl;
}

template <>
struct TemplateClass<int, int>
{
public:
	TemplateClass(int t, int u)
	{
		cout << "全特化版本: construct()" << endl;
		this->m_t = t;
		this->m_u = t;
	}

	void func()
	{
		cout << "全特化版本: func()" << endl;
		cout << typeid(this->m_t).name() << endl;
		cout << typeid(this->m_u).name() << endl;
	}

	void func1();
private:
	int m_t;
	int m_u;
};

void TemplateClass<int, int>::func1()
{
	cout << "类外定义函数" << endl;
}

int main()
{
	TemplateClass<int, double> tc(1, 2.2);
	tc.func();
	tc.func1();

	cout << "============" << endl;

	TemplateClass<int, int> tc2(1, 2);
	tc.func();
	tc.func1();

	return 0;
}

输出

泛化版本: construct()
泛化版本: func()
int
double
类外定义函数
============
全特化版本: construct()
泛化版本: func()
int
double
类外定义函数

全特化后的类相当于完全不同的一个类了,只是类名相同,里面的所有属性,函数如果要用都需要重新写。

普通成员函数的全特化

有一个类HashTools的泛型类。有一个get_hash函数。

为这个成员函数写了偏特化版本,当模板T是int的时候,调用这个函数会调用这个版本。

在C++STL的哈希中,偏特化有类似的应用。当需要计算的某个类型是int,是double对于哈希值的计算可以有不同的算法。

template <>
string HashTools<int>::get_hash()
{
	return "int";
}

下面是完整的代码

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class HashTools
{
public:
	HashTools(T key)
	{
		cout << "泛化版本: construct()" << endl;
		cout << typeid(this->m_key).name() << endl;
		this->m_key = key;
	}
	
	string get_hash()
	{
		return "...";
	}

private:
	T m_key;
};

template <>
string HashTools<int>::get_hash()
{
	return "int";
}

template <>
string HashTools<double>::get_hash()
{
	return "double";
}

int main()
{
	HashTools<int> t1(1);
	HashTools<float> t2(1.1);
	HashTools<double> t3(1.2);

	cout << t1.get_hash() << endl;
	cout << t2.get_hash() << endl;
	cout << t3.get_hash() << endl;

	return 0;
}

输出

泛化版本: construct()
int
泛化版本: construct()
float
泛化版本: construct()
double
int
...
double

静态成员变量的全特化

首先是一个泛型模板类中有静态成员变量的例子

class TemplateClass
{
public:
	TemplateClass() {}

public:
	static int m_static;
};

template <typename T, typename U>
int TemplateClass<T, U>::m_static = 50;

int main()
{
	cout << TemplateClass<double, int>::m_static << endl;
	return 0;
}

我们添加一行

int TemplateClass<double, int>::m_static = 100;

我希望在类模板T是double, U是int的时候,这个静态变量的值是100

然后我们测试

cout << TemplateClass<double, int>::m_static << endl;	// 输出100

完整代码

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass()
	{
	}

public:
	static int m_static;
};

template <typename T, typename U>
int TemplateClass<T, U>::m_static = 50;

int TemplateClass<double, int>::m_static = 100;

int main()
{
	cout << TemplateClass<int, int>::m_static << endl;		// 50
	cout << TemplateClass<double, int>::m_static << endl;	// 100
	return 0;
}

注意:

如果进行了普通成员函数的全特化,或者是静态成员变量的全特化,那么,就无法用这些全特化时指定的类型来对整个类模板进行全特化了

三个加粗部分刚好是上面的三个例子。

类模板的偏特化

  • 模板参数数量的偏特化

将模板类中的某些类型预定义。比如下面的第二个类,把T固定为类型float。如果初始化给的是float。那么构造的就是第二类(偏特化版本)的类。

#include <iostream>
#include <string>

using namespace std;

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "泛化" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}

};

template <typename U>
class TemplateClass<float, U>
{
public:
	TemplateClass(float t, U u)
	{
		cout << "偏特化" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

int main()
{
	TemplateClass<double, int> t1(1.1, 2);
	cout << "============" << endl;
	TemplateClass<float, int> t2(1.1, 2);
	return 0;
}
  • 模板参数范围偏特化

int -> const int 类型变小

T->T*, T->T&, T->T&&。类型范围上变小了。

和函数模板范围偏特化是同理的

核心部分是类开头,特化的类名后面都要更尖括号进一步说明

template <typename T, typename U>
class TemplateClass<const T, U*>
{
public:
	TemplateClass(const T t, U *u)
    ...

完整案例

#include <iostream>
#include <string>

using namespace std;

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "泛化" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};


template <typename T, typename U>
class TemplateClass<const T, U*>
{
public:
	TemplateClass(const T t, U *u)
	{
		cout << "偏特化" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

int main()
{
	TemplateClass<double, int> t1(1.1, 2);
	
	cout << "==========" << endl;

	int a = 1;
	int *pa = &a;
	TemplateClass<const int, int*>(a, pa);

	return 0;
}

输出

泛化
double
int
==========
偏特化
int
int *

缺省参数

基本范例

类型模板参数缺省值的规矩:如果某个模板参数有缺省值,那么从这个有缺省值的模板参数开始,后面的所有模板参数都得有缺省值。

简单来说

template <typename T = int, typename U = int>	// ok
template <typename T = int, typename U>	// error
template <typename T, typename U = int>	// ok

例子:

假设有这样的模板

template <typename T = int, typename U = int>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "template class 1" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

我们可以怎么写。不写<>他会默认认为是 int, int

TemplateClass t1(1, 2);

或者我们写

TemplateClass<int, double> t3(1.1, 1.2);

他会去推断成<int, double>的模板。

值得注意的是,如果我们

	TemplateClass t2(1.2, 2.2);

这时候T和U的类型都是double。它并没有使用默认模板参数。而是构造了double, double版本。再次印证,模板在编译时进行代码的处理,类型替换。无法进行类型装换。

但是如果我们不用默认类型

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "template class 1" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

在调用时

	TemplateClass t1(1, 2);						
	TemplateClass t2(1.2, 2.2);					
	TemplateClass<int, double> t3(1.1, 1.2);	

输出结果是一样的,同样的无法在编译时做类型转换,没写<>的编译器会直接推断类型,生成对应的模板版本。似乎默认类型是多此一举???

一些特殊点

  • 类模板片特化版本中的类型模板参数不可以有缺省值

我们搞出了偏特化版本。如下

template <typename T, typename U>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "template class 1" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

template <typename U>
class TemplateClass<int, U>
{
public:
	TemplateClass(int t, U u)
	{
		cout << "template class 2" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

如果我们对偏特化版本的类型做默认参数,那么会编译错误.

比如这样

template <typename U = int>		// 这里导致编译错误
class TemplateClass<int, U>
{
public:
	TemplateClass(int t, U u)
	{
		cout << "template class 2" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

模板参数相互依赖

后面的模板参数依赖前面的模板参数

#include <iostream>
#include <string>

using namespace std;

template <typename T, typename U = T*>
class TemplateClass
{
public:
	TemplateClass(T t, U u)
	{
		cout << "template class 1" << endl;
		cout << typeid(t).name() << endl;
		cout << typeid(u).name() << endl;
	}
};

int main()
{
	double v = 1.1;
	TemplateClass<double> t1(v, &v);
	return 0;
}

第二个模板类型U是,T类型的指针类型

template <typename T, typename U = T*>

那么你在写的时候就可以只写一个类型T

	double v = 1.1;
	TemplateClass<double> t1(v, &v);

模板声明中指定缺省参数

不推荐使用,花里胡哨的。。。

template <typename T, typename U, typename V = int, typename W = char>
struct TC;

// V, W没有直到类型,开始找,找到上面的int char。
template <typename T, typename U = char, typename V, typename W>
struct TC;

template <typename T, typename U, typename V, typename W>
struct TC;

类型别名

模板一般比较长

所以一般用typedef和 using (c++11以上)简化代码

typedef TC<int, float> IF_TC;
using IF_TCU = TC<int, float>;

非类型模板参数

template <typename T, typename U, auto size = 8>
struct TC
{
	T m_arr[arrsize];
	
	void func();
};

这里的auto写int, size_t什么同理。

这里的arrsize在编译期间就能确定了。

定义函数时候的写法:

template <typename T, typename U, auto size>
void TC<T, U, size>::func()
{
	cout << "func" << endl;
}

注意这里的void TC<T, U, size>::func()的第三个参数是写参数size非类型模板参数

如果函数定义写在类内部

template <typename T, typename U, int size = 8>
struct TC
{
	T m_arr[size];
	
	template <typename T, typename U, int size>
	void func()
	{

	}
};

调用

	TC<int, int, 5> t1;

另一个例子:

template <const char *p>
struct TC
{
	TC()
	{
		cout << p << endl;
	}
};

const char g_s[] = "hello";

构造 TC<g_s> t1;输出hello没问题。

但是如果我把const char g_s[] = "hello";改成 const char* g_s = "hello";就会出错。全局指针不能作为函数参数

如果我们调用 TC<"hello"> t1;,编译也会出错。字符串常量也无法作为模板参数

模板还是有很多限制的,比如非类型模板参数就不能是double,因为他不是一个精确的数字。

类型还是在一定程度上受限的。上面展示了int和指针版本。

成员函数模板

基本概念和构造函数模板

template <typename T1>
class A
{
public:
	template <typename T2>
	A(T1 t1, T2 t2);

	template <typename T3>
	void func(T3 t3)
	{
		cout << "T3:" << typeid(t3).name() << endl;
	}
};

template <typename T1>
template <typename T2>
A<T1>::A(T1 t1, T2 t2)
{
	cout << "construct" << endl;
	cout << "T1:" << typeid(t1).name() << endl;
	cout << "T2:" << typeid(t2).name() << endl;
}

类模板可以有泛型T1。然后在成员函数(构造函数,拷贝构造,成员函数。。。)写上其他的泛型类型T2, T3。

// CppObjectMode.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <string>

using namespace std;

template <typename T1>
class A
{
public:
	template <typename T2>
	A(T1 t1, T2 t2);
public:

	A(double a, double b)
	{
		cout << "A(double a, double b) construct" << endl;
	}

	A(T1 a, T1 b)
	{
		cout << "A(T1 a, T1 b) construct" << endl;
	}

	template <typename T3>
	void func(T3 t3)
	{
		cout << "T3:" << typeid(t3).name() << endl;
	}
};


template <typename T1>
template <typename T2>
A<T1>::A(T1 t1, T2 t2)
{
	cout << "construct" << endl;
	cout << "T1:" << typeid(t1).name() << endl;
	cout << "T2:" << typeid(t2).name() << endl;
}

int main()
{
	A<float> a(1, 2);
	A<float> b(1.1, 2.2);
	A<float> c(3.3f, 4.4f);
	return 0;
}


输出

construct
T1:float
T2:int
A(double a, double b) construct
A(T1 a, T1 b) construct

a) 类模板中的成员函数,只有源程序代码中出现调用这些成员函数的代码时,这些成员函数才会出现在一个实例化了的类模板中。

b) 类模板中的成员函数模板,只有源程序代码中出现调用这些成员函数模板的代码时,这些成员函数模板的具体实例才会出现在一个实例化了的类模板中。

ps: 简单来说就是你写了要是不调用,编译器就有可能忽略你写的东西。对开发无影响

c) 目前编译器并不支持虚成员函数模板,因为虚函数表vtbl的大小是固定的,成员函数模板只有被调用才能初始化出来。也就是虚函数(他是一个模板)有可能没被调用,没被构造除了,虚函数没构造出来。造成vtbl大小没法确定。

C++之父的说法:如果允许虚函数模板,则每次有人用新的参数类型调用该虚函数模板时,就必须给对应的虚函数表再增加一项,这意味着只有链接程序才能去构造虚函数表并在表中设置有关函数,因此,成员函数模板绝不能是虚的。

类模板中可以有普通的虚成员函数(虚函数)。普通成员函数如果不被调用的情况下不会被实例化出来。但是,对于虚函数,不管是否调用,编译器都会把他实例化出来,因为编译器要创建虚函数表vtbl,该表中的每个具体表项都对应一个虚函数地址,所以编译器必然得把所有虚函数都实例化出来。

类模板中可以有虚函数(这里我的理解是类模板的函数体中,你想调用虚函数没问题)。虚函数不能是函数模板写的。

拷贝构造模板和拷贝赋值模板

拷贝构造模板不是拷贝构造函数

拷贝赋值运算符模板不是拷贝构造运算符

因为拷贝构造函数或者拷贝赋值运算符要求拷贝的对象类型完全相同,而拷贝构造函数模板和拷贝赋值运算符模板就没有这种要求

#include <iostream>
#include <string>

using namespace std;

template <typename T1>
class A
{
public:
	A(int v)
	{
		m_v = v;
	}
public:
	template <typename U>
	A(const A<U>& other)
	{
		cout << "A(const A<U>& other)" << endl;
	}

	template <typename U>
	A<T1>& operator= (const A<U>& other)
	{
		cout << "A<U>& operator= (const A<U>& other)" << endl;
		return *this;
	}
public:
	int m_v;
};

int main()
{
	A<int> a1(1);
	a1.m_v = 666;

	A<int> a2(a1);
	cout << a2.m_v << endl;

	return 0;
}

运行后只输出了666

我们发现A<int> a2(a1);没有导致

	template <typename U>
	A(const A<U>& other)
	{
		cout << "A(const A<U>& other)" << endl;
	}

拷贝构造函数函数模板的执行。

但是输出666。说明a2还是拷贝了a1的值的。但是它执行了哪里的代码呢?拷贝构造函数模板有啥用呢。

其实他调用的是拷贝构造函数,而不是拷贝构造函数模板

A(const A &other)
{
	cout << "copy" << endl;
	this->m_v = other.m_v;
}

这里要说明的只是

拷贝构造模板不是拷贝构造函数,拷贝赋值运算符模板不是拷贝构造运算符

这个问题

完整代码。

template <typename T1>
class A
{
public:
	A(int v)
	{
		m_v = v;
	}
public:
	template <typename U>
	A(const A<U>& other)
	{
		cout << "A(const A<U>& other)" << endl;
	}
	
    // 拷贝构造函数
	A(const A &other)
	{
		cout << "copy" << endl;
		this->m_v = other.m_v;
	}

	template <typename U>
	A<T1>& operator= (const A<U>& other)
	{
		cout << "A<U>& operator= (const A<U>& other)" << endl;
		return *this;
	}
public:
	int m_v;
};

拷贝构造函数模板什么时候被调用呢???

上面例子没被调用的原因是

A<int> a1(1);
a1.m_v = 666;

A<int> a2(a1);
cout << a2.m_v << endl;

a1和a2都是int。不符合U和T1类型不同的规则。

上面拷贝构造函数模板缺了点东西

	template <typename U>
	A(const A<U>& other)
	{
		cout << "A(const A<U>& other)" << endl;
		this->m_v = other.m_v;
	}

漏了这个

this->m_v = other.m_v;

然后我们这样调用

	A<int> a1(1);
	a1.m_v = 666;

	A<float> a2(a1);
	cout << a2.m_v << endl;

输出

A(const A<U>& other)
666

成功调用拷贝构造函数模板

拷贝赋值就是类似的原理过程了。略。。。

成员函数模板的特化

成员函数模板的特化问题

下面展示了函数泛化,偏特化,全特化的例子。

成员函数模板和类模板已经基本没关系了。根据具体的类型推断调用的具体函数。

ps: 个人感觉到底调用谁还是很混乱的。。。

#include <iostream>
#include <string>

using namespace std;

template <typename T1, typename T2>
class A
{
public:
	A(T1 t1, T2 t2);
public:
	template <typename T3, typename T4>
	void func(T3 t3, T4 t4)
	{
		cout << "成员函数模板" << endl;
		cout << typeid(t3).name() << endl;
		cout << typeid(t4).name() << endl;
	}

	template <typename T4>
	void func(int* t3, T4 t4)
	{
		cout << "偏特化" << endl;
		cout << typeid(t3).name() << endl;
		cout << typeid(t4).name() << endl;
	}

	template <>
	void func(double t3, int t4)
	{
		cout << "全特化" << endl;
		cout << typeid(t3).name() << endl;
		cout << typeid(t4).name() << endl;
	}
public:
	T1 m_t1;
	T2 m_t2;
};

template <typename T1, typename T2>
A<T1, T2>::A(T1 t1, T2 t2)
{
	cout << "构造函数执行" << endl;
	this->m_t1 = t1;
	this->m_t2 = t2;
}

int main()
{
	int val = 1;
	A<int, float> a(1, 2.2);			// 构造函数执行
	a.func<float, float>(1, 1.1);		// 成员函数模板	float  float
	a.func(&val, 1.0);					// 偏特化		int*   double
	a.func<int*, float>(&val, 1.0);		// 成员函数模板	int*   float
	a.func<double, int>(1.2, 1);		// 全特化		double int
	return 0;
}

输出。同时输出也注释在代码后面

构造函数执行
成员函数模板
float
float
偏特化
int *
double
成员函数模板
int *
float
全特化
double
int

几个有意思的点

	a.func(&val, 1.0);					// 偏特化		int*   double
	a.func<int*, float>(&val, 1.0);		// 成员函数模板	int*   float

emmmm....

这不小心容易出事啊。最开始怎么弄都调用不到偏特化的版本,然后瞎弄。

最后在不写<>的时候才调用到。

编译器是有一套寻找匹配规则的。

另外的写法:

原来的函数模板是写在类内的。

	template <typename T4>
	void func(int* t3, T4 t4)
	{
		cout << "偏特化" << endl;
		cout << typeid(t3).name() << endl;
		cout << typeid(t4).name() << endl;
	}

如果要写在类外的话,要这么写

template <typename T1, typename T2>
class A
{
	...
    // 声明
	template <typename T4>
	void func(int* t3, T4 t4);
	...
};

// 定义
template <typename T1, typename T2>
template <typename T4>
void A<T1, T2>::func(int* t3, T4 t4)
{
	cout << "偏特化" << endl;
	cout << typeid(t3).name() << endl;
	cout << typeid(t4).name() << endl;
}
template <typename T1, typename T2>
template <typename T4>

这个地方有点,。。


有些资料上说目前的C++标准不允许在类模板之外全特化一个未被特化的类模板(指的是类模板A)的成员函数

ps: 所以按上面偏特化的写法,把全特化写到类外面有问题。这里就不展示了。

整体感觉:类模板中的成员函数全特化可能还不算太完善,写代码时要注意测试

在实际工作中,建议把这些特化版本写在类模板内部,然后类模板一般也要写在头文件。

类模板嵌套

template <typename T1>
class A
{
public:
	A(int v)
	{
		this->m_v = v;
	}

	template <typename T2>
	class B
	{
	public:
		B(int v)
		{
			this->m_v = v;
			T1 a;
			T2 b;
		}
	public:
		int m_v;
	};
public:
	int m_v;
};

使用

	A<int> a(1);
	A<int>::B<int> b(2);

模板嵌套。A<int>::B<int> b(2);前面的<>也不能省去。

在B的构造函数中能够获得外面模板的T1类型。

例子:

如果把上面定义在A类内的函数模板放到A类外

template <typename T1>
class A
{
public:
	template <typename T2>
	class B
	{
	public:
		void func(T1 t1, T2 t2);
	};

};

template <typename T1>
template <typename T2>
void A<T1>::B<T2>::func(T1 t1, T2 t2)
{
	cout << "func()" << endl;
	cout << typeid(t1).name() << endl;
	cout << typeid(t2).name() << endl;
}

int main()
{
	A<float>::B<double> b;
	b.func(1.1, 2.2);
	return 0;
}

无非就是作用于多写几层了。

void A<T1>::B<T2>::func(T1 t1, T2 t2)

变量模板和成员变量模板

变量模板的特化(C++14)

template <typename T>
T g_var {};	// 写成 = 0 初始化一般没问题,但是只使用数值类型

int main()
{
	g_var<float> = 66.6;
	g_var<int> = 10;
	cout << g_var<float> << endl;
	cout << g_var<int> << endl;
	return 0;
}

其中 T g_var {};大括号的写法叫零初始化。数值0, 指针nullptr, bool false。

变量模板的全特化,偏特化。

这里我的VS有红下划线提示错误,但是能过编译。编译版本C++17

#include <iostream>
#include <string>

using namespace std;

// 泛化
template <typename T>
T g_var {};

// 全特化
// 变量模板特化的时候,并不需要正在特化的类型(double)与这个变量模板的类型(char)保持一致
template <>
char g_var<double> {};

// 偏特化
// 尖括号里面的是正在特化的类型
// 要求后面的类型依赖前面的类型 T* 依赖于 T
template <typename T>
T g_var<T*> { 120 };

int main()
{
	g_var<double> = '2';
	cout << g_var<double> << endl;					// 输出 2
	cout << typeid(g_var<double>).name() << endl;	// 输出 char
	cout << "===========" << endl;
	cout << g_var<int*> << endl;	// 输出 120 偏特化版本
	cout << g_var<int> << endl;		// 输出 0 泛化版本
	return 0;
}

变量模板也可以给默认参数

template <typename T = int>
T g_var { 10 };

int main()
{
	cout << g_var<> << endl;
	return 0;
}

输出10

非类型模板参数

也就是说,尖括号里面也可以写数字。例如:

template <typename T = int, int sz = 10>
T g_var [sz];


int main()
{
	g_var<int, 10>;
	// 这里我还取不到 g_var<int, 10> 的 sz。必须写10
    for (int i = 0; i < 10; i++)	
    {
		g_var<int, 10>[i] = i;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << g_var<int, 10>[i] << endl;
	}
	return 0;
}

变量模板的另一种形式

template <typename T>
struct B
{
	// const也可以写成constexpr, {} 也可以不加
	const static T value = { 160 };
}; 

template <typename T>
int g_var = B<T>::value; // 注意g_var是个变量模板

int main()
{
	//cout << g_var<double> << endl;	// 编译错误
	cout << &g_var<int> << endl;		// 输出 DDC008
	cout << &B<int>::value << endl;		// 输出 DD9B38

	cout << g_var<int> << endl;		// 输出 160
	cout << B<int>::value << endl;	// 输出 160
	g_var<int> = 666;
	cout << g_var<int> << endl;		// 输出 666
	cout << B<int>::value << endl;	// 输出 160
	return 0;
}

简单来说就是用一个常量初始化了变量模板。这个变量模板刚好是另一个模板类的静态变量。

成员变量模板

template <typename T1>
class A
{
public:
	template <typename T2>
	static T2 m_v;			// 静态成员变量模板声明
};

template <typename T1>
template <typename T2>
T2 A<T1>::m_v = 6;			//定义


int main()
{
	cout << A<double>::m_v<int> << endl;	// 输出 6
	A<double>::m_v<int> += 1;
	cout << A<double>::m_v<int> << endl;	// 输出 7
	return 0;
}

别名模板与成员别名模板

template<typename T>
using str_map_t = std::map<std::string, T>;

案例

#include <iostream>
#include <string>
#include <map>

using namespace std;

template<typename T>
class E
{
	template<typename T>	// 成员别名模板
	using str_map_t = map<string, T>;

public:
	str_map_t<int> map1;	// 现在这是一个 <string, int> 的 map了
};

int main()
{
	E<double> e;
	e.map1["key"] = 1.0;
	cout << e.map1["key"] << endl;						// 1
	cout << typeid(e.map1["key"]).name() << endl;		// int
	return 0;
}

注意这里

template<typename T>
class E
{
	template<typename T>	// 成员别名模板
	using str_map_t = map<string, T>;

这里模板的T和using上面的。两个T不是一个东西。

比如你

void func1(int a);

void func2(int a);

这两个a显然不是一个东西。

模板模板参数

让模板参数成为模板

#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>

using namespace std;

// Container 叫模板模板参数
template <
	typename T, 
	template <class> class Container = vector>
class LinearContainer
{
public:
	Container<T> _container;
};

int main()
{
	LinearContainer<int, list> c1;
	LinearContainer<double, vector> c2;
	return 0;
}

出现了,c++stl中,容器内存分配器的写法。

其中,两种等价写法。这里的W可省略。

	template <class> class Container = vector>
	template <typename W> typename Container = vector>

共用体模板

template <typename T, typename U>
union Union
{
	T a;
	U b;
};

类模板中的友元类

友元概念回顾:

让某个类B称为另外一个类A的友元类,这样的话,类B就可以在其成员函数中访问类A的所有成员,而不管这些成员在类A中是用什么(public,protected,private)来修饰的

如果现在类A和类B都变成了类模板,那么能否让类模板B成为类模板A的友元类模板呢?

让类模板的某个实例成为友元类

template <typename U>
class B;

template <typename T>
class A
{
	friend class B<int>;
private:
	int data;
};

template <typename U>
class B
{
public:
	void func()
	{
		A<int> a;
		a.data = 1;
	}
};

int main()
{
	B<int> b;
	b.func();
	return 0;
}

注意几个地方。首先是类模板的声明,因为B定义在A的下面

template <typename U>
class B;

第二个是友元的声明

	friend class B<int>;

同时,在B的共有函数中,

	void func()
	{
		A<int> a;
		a.data = 1;
	}

访问了A的私有变量,由于友元,可以访问。注意这里只针对int的模板。

friend class B<int>;

如果这里改成friend class B<long>;就不行了

这里的long对应的是

	void func()
	{
		A<long> a;
		a.data = 1;
	}

这里的long。

注意这句话

让类模板的某个实例成为友元类

让类模板成为友元类模板

我们在上面的基础上做修改

template <typename U>
class B;

template <typename T>
class A
{
	template<typename> friend class B;	
    // 核心是这一行的修改。B成为了A的朋友,所有B可以访问A的东西。
private:
	int data;
};

template <typename U>
class B
{
public:
	void func()
	{
		A<int> a;
		a.data = 1;
	}
};

int main()
{
	B<int> b;
	b.func();
	return 0;
}

我们让整个类模板成为某个类的盆友

让类型模板参数成为友元类 (C++11)

如果传递进来的类型模板参数是一个类类型,则这个类类型可以成为当前类模板的友元类

说起来复杂,看例子就很简单了。

template <typename T>
class A
{
public:
	friend T;
private:
	int m_data;
};

class B
{
public:
	void func()
	{
		A<B> a;
		a.m_data = 1;
		cout << a.m_data << endl;

		A<int> b;
		b.m_data = 1;				// error
		cout << b.m_data << endl;	// error
	}
};

int main()
{
	B b;
	b.func();

	return 0;
}

类模板中的友元函数

让函数模板的某个实例成为友元函数

一个显然错误的例子

class A
{
private:
	void func()
	{
		cout << "A::func() called" << endl;
	}
};

template <typename U, typename V>
void func(U u, V v)
{
	cout << "u = " << u << endl;
	cout << "v = " << v << endl;
	A a;
	a.func();
}

int main()
{
	::func<int, int>(1, 2);
	return 0;
}

由于访问权限,编译错误。

然后我们尝试吧全局的func模板写成类A的友元函数。

template <typename U, typename V>			// 注意这里要加声明语句
void func(U u, V v);

class A
{
	friend void ::func<int, int>(int, int);	//添加一行
private:
	void func()
	{
		cout << "A::func() called" << endl;
	}
};

这样我们就可以调用了

	::func<int, int>(1, 2);

但是如果

	::func<int, double>(1, 2.0);

编译又出错了。我们需要在类A中继续添加<int, double>模板的友元声明

	friend void ::func<int, double>(int, double);

友元函数模板

上面每个类型友元都单独写,显然比较麻烦。一种写法是让其成为友元函数模板。

template <typename U, typename V>
void func(U u, V v);

class A
{
	template <typename U, typename V> friend void ::func(U u, V v);
private:
	void func()
	{
		cout << "A::func() called" << endl;
	}
};

template <typename U, typename V>
void func(U u, V v)
{
	cout << "u = " << u << endl;
	cout << "v = " << v << endl;
	A a;
	a.func();
}

int main()
{
	::func<int, int>(1, 2.0);
	::func<int, float>(1, 2.0);
	::func<int, double>(1, 2.0);
	return 0;
}

核心代码

	template <typename U, typename V> friend void ::func(U u, V v);

类模板中定义友元函数

这种友元函数是能被调用的。而且只有被调用的时候才会被实例化改函数。

template <typename T>
class A
{
public:
	template <typename T>
	friend void func(A<T> &a)
	{
		a.private_func();
	}
private:
	void private_func()
	{
		cout << "private_func" << endl;
	}
};

int main()
{
	A<int> a;
	func(a);
	return 0;
}

通过一个模板友元函数,调用该类的私有函数。

未完待续。。。

可变参数模板

英文:Variadic Templates ,C++11

允许模板定义中包含 0 ,到多个(任意个)模板参数。

很多特效在 C++17 中添加

基本使用

基本案例:

#include <iostream>

using namespace std;

template <typename... T>	// 	T 是 一堆类型	
void func(T... args)		//  args 是一堆参数
{
	cout << sizeof... (args) << endl;
	cout << sizeof... (T) << endl;
	cout << "================" << endl;
}

int main()
{
	func();
	func(1, 2.2, "123");
	func<double, double>(1, 1, 1);
	return 0;
}

输出

0
0
================
3
3
================
3
3
================

新问题,参数如何展开?

套路基本是递归展开

#include <iostream>

using namespace std;

void func()
{
	cout << "终止函数" << endl;
}

template <typename T, typename... U>
void func(T first, U... other_args)
{
	cout << first << endl;
	func(other_args...);
}

int main()
{
	func(1, 2.2, "123");
	return 0;
}

C++17中增加了一个语句,叫做“编译期间if语句(constexpr if)

if constexpr()...

如果用这个特效就不用写终止函数。

修改模板函数为

template <typename T, typename... U>
void func(T first, U... other_args)
{
	cout << first << endl;
	if constexpr (sizeof... (other_args) > 0)
	{
		func(other_args...);
	}
	else
	{

	}
}

调用

	func(1, 2.2, "123");

发现一个有趣的现象

template <typename T, typename... U>
void func(T first, U... other_args)
{
	cout << first << endl;
	if constexpr (sizeof... (other_args) <= 0)
	{
		return;
	}
	else
	{
		func(other_args...);
	}
}

下面这样会编译错误。只因为else被注释掉了。

说明if constexpr ... else 是一对。去掉后它失去了编译时判断函数终止的能力。

template <typename T, typename... U>
void func(T first, U... other_args)
{
	cout << first << endl;
	if constexpr (sizeof... (other_args) <= 0)
	{
		return;
	}
	//else
	{
		func(other_args...);
	}
}

可变参函数重载

可变参数模板也可以重载。类似下面这种感觉

#include <iostream>

using namespace std;

template <typename T, typename... U>
void func(T first, U... other_args)
{
	cout << "func1:" << first << endl;
	if constexpr (sizeof... (other_args) <= 0)
	{
		return;
	}
	else
	{
		func(other_args...);
	}
}

template <typename T, typename... U>
void func(T *first, U*... other_args)
{
	cout << "func2:" << first << endl;
	if constexpr (sizeof... (other_args) <= 0)
	{
		return;
	}
	else
	{
		func(other_args...);
	}
}

int main()
{
	func(1, 2.2, 3);
	int a = 1;
	int b = 2;
	int c = 3;
	func(&a, &b, &c);
	return 0;
}

折叠表达式(c++17)

目的是计算某个值。

该值得特殊性在于:它与所有可变参有关,而不是与单独某个可变参有关。 需要所有可变参都参与计算,才能求出该值

折叠表达式有四种格式:一元左折、一元右折,二元左折、二元右折

注意语法上必须要括号包着

一元左折

格式: (... 运算符 一包参数)

计算方式:((( 参数1 运算符 参数2) 运算符 参数3)....运算符 参数N)

template <typename... T>
auto add_val(T... args)
{
	return (... + args);
}

cout << add_val(1, 2, 3, 4, 5) << endl;

一元右折

格式: (一包参数 运算符 ...)

计算方式:( 参数1 运算符 ( ... (参数N-1 运算符 参数N)))

template <typename... T>
auto add_val(T... args)
{
	return (args + ...);
}
cout << add_val(1, 2, 3, 4, 5) << endl;

一个比较一元左折和右折的例子

#include <iostream>

using namespace std;

// 一元右折
template <typename... T>
auto cut_val_1(T... args)
{
	return (args - ...);
}

// 一元左折
template <typename... T>
auto cut_val_2(T... args)
{
	return (... - args);
}

int main()
{
	/**
		输出 -2
		计算
		1 - (2 - (3 - 4))
	*/
	cout << cut_val_1(1, 2, 3, 4) << endl;
	/**
		输出 -8
		计算
		((1 - 2) - 3) - 4
	*/
	cout << cut_val_2(1, 2, 3, 4) << endl;
	return 0;
}

二元左折

格式:(init 运算符 ... 运算符 一包参数)

计算方式:(((init 运算符 参数1) 运算符 参数2) ... 运算符 参数N)

init是一个初始值

template <typename... T>
auto sub_val_left_b(T... args)
{
	return (10 - ... - args); // 二元左折:10, 两个符号还必须一样?。。。。输出的是0
}
cout << sub_val_left_b(1, 2, 3, 4) << endl;

计算过程

((10 - 1) - 2) - 3) - 4) = 0

另一个例子

#include <iostream>

using namespace std;

template <typename... T>
void sub_val_left_b(T... args)
{
	(cout << ... << args); // 二元左折:现在 cout 是 init, << 是符号
}

int main()
{
	sub_val_left_b(1, "qwe", 3, 4.5);
	return 0;
}

输出

1qwe34.5

计算过程

((((cout << 1) << "qwe") << 3) << 4.5)

二元右折

格式:(一包参数 运算符 ... 运算符 init)

计算方式:(参数1 运算符 (...(参数N 运算符 init )))

#include <iostream>

using namespace std;

template <typename... T>
auto sub_val_right_b(T... args)
{
	return (args - ... - 10); // 二元右折
}
int main()
{
	cout << sub_val_right_b(1, 2, 3, 4) << endl;
	// 1 - (2 - (3 - (4 - 10))) = 8
	return 0;
}

可变参数表达式

template <typename... T>
auto print_result(T const& ... args)
{
	(cout << ... << args) << endl;
	return (... + args);
}

template <typename... T>
void  print_calc(T const& ... args)
{
	cout << print_result(2 * args...) << endl;
}

int main()
{
	print_calc(1, 2, 3, 4);
	return 0;
}

注意

	cout << print_result(2 * args...) << endl;

中的 (2 * args...)就是可变参数表达式

相当于吧 1 2 3 4 每个参数 * 2 后再传到 print_result 里面

这又是一个 二元左折表达式的例子。

还有一些等价的写法。

	print_result(2 * args...);
	print_result(args * 2 ...);
	print_result((args * 2) ...);
	print_result(args + args...);

可变参类模板

可变参类模板:允许模板定义中包含0到多个(任意个)模板参数。

通过递归继承方式展开类型、非类型、模板模板参数包

类型模板参数包的展开(通过递归继承方式)

#include <iostream>

using namespace std;

// 泛化版本类模板
template <typename... Args>
class A
{
public:

};

// 也可以只声明,不写类的内容,像下面这样
// template <typename... Args>
// class A;

// 偏特化版本,注意必须有前面的泛化版本
template <typename First, typename ... Others>
class A<First, Others...>: private A<Others...>
{
public:
	A() : m_v(0)
	{
		cout << "A() 偏特化 构造函数" << endl;
		cout << "this:" << this << endl;
		cout << sizeof...(Others) << endl;
	}

public:
	First m_v;
};


template <>
class A<>	//	一个特殊的特化版本,看起来像全特化,但是不是全特化
{
public:
	A()
	{
		cout << "A<> 结束" << endl;
	}
};

int main()
{
	A<int, int, float, double> a;
	return 0;
}

输出

A<> 结束
A() 偏特化 构造函数
this:009FF940
0
A() 偏特化 构造函数
this:009FF940
1
A() 偏特化 构造函数
this:009FF940
2
A() 偏特化 构造函数
this:009FF940
3

模板直接弄出好多个类,他们还互相继承的感觉。

简单写个继承关系图。。。

花里胡哨的。。。

A<>
    |
    v
A<int>
    |
    v
A<int, int>
    |
    v
A<int, int, float>
    |
    v
A<int, int, float, double>

构造的时候父类会早与子类,所以最开始调用的是一个特殊版本的写法。

这个看起来像全特化,其实不能这么叫。

template <>
class A<>	//	一个特殊的特化版本,看起来像全特化,但是不是全特化
{
public:
	A()
	{
		cout << "A<> 结束" << endl;
	}
};

// 输出
// A<> 结束

稍加修改

下面的例子展现了解析的过程。

#include <iostream>

using namespace std;

// 泛化版本类模板
template <typename... Args>
class A
{
public:

};

// 也可以只声明,不写类的内容,像下面这样
// template <typename... Args>
// class A;

// 偏特化版本,注意必须有前面的泛化版本
template <typename First, typename ... Others>
class A<First, Others...>: private A<Others...>
{
public:
	A() : m_v(0)
	{
		cout << "A() 偏特化 构造函数" << endl;
		cout << "this:" << this << endl;
		cout << sizeof...(Others) << endl;
	}

	A(First v1, Others ...args) : m_v(v1), A<Others...>(args...)
	{
		cout << typeid(v1).name() << " " << v1 << endl;
	}
public:
	First m_v;
};


template <>
class A<>	//	一个特殊的特化版本,看起来像全特化,但是不是全特化
{
public:
	A()
	{
		cout << "A<> 结束" << endl;
	}
};

int main()
{
	A<int, int, float, double> a(1, 2, 3.0, 4.0);
	return 0;
}

添加了有参数的构造函数

	A(First v1, Others ...args) : m_v(v1), A<Others...>(args...)
	{
		cout << typeid(v1).name() << " " << v1 << endl;
	}

。。。我为啥在这里学泛型

输出

A<> 结束
double 4
float 3
int 2
int 1

这种例子在C++标准库中的 tuple有类似的写法。

非类型模板参数包展开(通过递归继承方式)

#include <iostream>

using namespace std;

template <int ...FArgs>
class A
{
public:
	A()
	{
		cout << "构造函数" << endl;
	}
};

template <int First, int ...Others>
class A<First, Others...> : private A<Others...>
{
public:
	A()
	{
		cout << "first = " << First << endl;
	}
};

int main()
{
	A<1, 2, 3> a;
	return 0;
}

输出

构造函数
first = 3
first = 2
first = 1

模板模板参数包的展开(通过递归继承方式)

#include <iostream>
#include <vector>
#include <list>
#include <queue>

using namespace std;

template <typename T,
		  template <typename> typename ... Container>
class A
{
public:
	A()
	{
		cout << "==============================" << endl;
		cout << "泛化版本构造函数" << endl;
	}
};

template <typename T,
	template <typename> typename FirstContainer,
	template <typename> typename ... OtherContainer>
class A<T, FirstContainer, OtherContainer...>: private A<T, OtherContainer...>
{
public:
	A()
	{
		cout << "==============================" << endl;
		cout << "偏特化版本构造函数, this = " << this << endl;
		cout << "first = " << typeid(FirstContainer).name() << endl;
		cout << "other = " << typeid(OtherContainer).name() << endl;
		cout << "size  = " << sizeof...(OtherContainer) << endl;

		m_container.push_back(12);
	}
	FirstContainer<T> m_container;
};

template <typename T,
	template <typename> typename ... Container>
class B :private A<T, Container...>
{
public:
	B()
	{
		cout << "==============================" << endl;
		cout << "B() 构造, this = " << this << endl;
		cout << "T = " << typeid(T).name() << endl;
		cout << "参数个数 = " << sizeof...(Container) << endl;
	}
};

int main()
{
	B<int, vector, list, deque> b;
	return 0;
}

输出

==============================
泛化版本构造函数
==============================
偏特化版本构造函数, this = 010BFE04
first = class std::deque
other = class $$U$TBAAD
size  = 0
==============================
偏特化版本构造函数, this = 010BFE04
first = class std::list
other = class $$U$TBAAD
size  = 1
==============================
偏特化版本构造函数, this = 010BFE04
first = class std::vector
other = class $$U$TBAAD
size  = 2
==============================
B() 构造, this = 010BFE04
T = int
参数个数 = 3

它搞出了

A<int>
A<int, deque>
A<int, list, deque>
A<int, vector, list, deque>
B<int, vector, list, deque>

五个类

类型模板参数包的展开(通过递归组合方式展开参数包)

#include <iostream>

using namespace std;

// 泛化版本类模板
template <typename... Args>
class A
{
public:
	A()
	{
		cout << "==============================================" << endl;
		cout << "泛化版本构造" << endl;
	}
};

// 偏特化版本,注意必须有前面的泛化版本
template <typename First, typename ... Others>
class A<First, Others...>
{
public:
	A() : m_v(0)
	{
		cout << "==============================================" << endl;
		cout << "A() 偏特化 构造函数" << endl;
		cout << "this:" << this << endl;
		cout << sizeof...(Others) << endl;
	}

	A(First first, Others ...args) : m_v(first), m_o(args...)
	{
		cout << "==============================================" << endl;
		cout << "A(First first, Others ...args) 偏特化 构造函数" << endl;
		cout << "this: " << this << endl;
		cout << "sizeof... (Others): " << sizeof...(Others) << endl;
		cout << "first = " << m_v << endl;
		cout << "type = " << typeid(first).name() << endl;
	}
public:
	First m_v;
	A<Others...> m_o;
};


template <>
class A<>	//	一个特殊的特化版本,看起来像全特化,但是不是全特化
{
public:
	A()
	{
		cout << "==============================================" << endl;
		cout << "A<> 结束" << endl;
	}
};

int main()
{
	A<int, int, float, double> a(1, 2, 3.3f, 4.4);
	return 0;
}

输出

==============================================
A<> 结束
==============================================
A(First first, Others ...args) 偏特化 构造函数
this: 00DEF960
sizeof... (Others): 0
first = 4.4
type = double
==============================================
A(First first, Others ...args) 偏特化 构造函数
this: 00DEF958
sizeof... (Others): 1
first = 3.3
type = float
==============================================
A(First first, Others ...args) 偏特化 构造函数
this: 00DEF950
sizeof... (Others): 2
first = 2
type = int
==============================================
A(First first, Others ...args) 偏特化 构造函数
this: 00DEF948
sizeof... (Others): 3
first = 1
type = int

类型模板参数包的展开(通过tuple和递归调用展开参数包)

  • tuple 简单实用
	tuple<float, int, int> t(12.5f, 100, 52);
	cout << get<0>(t) << endl;
	cout << get<1>(t) << endl;
	cout << get<2>(t) << endl;

例子:

#include <iostream>
#include <map>
using namespace std;

template <int index, int max_size, typename ...T>
class A
{
public:
	static void func(const tuple<T...>& t)
	{
		cout << get<index>(t) << endl;
		A<index + 1, max_size, T...>::func(t);
	}
};

// index == max_size的时候会走偏特化,注意这里两个max_size
template <int max_size, typename ...T>
class A<max_size, max_size, T...>
{
public:
	static void func(const tuple<T...>& t)
	{
		
	}
};

template <typename ...T>
void func(const tuple<T...>& t)
{
	A<0, sizeof...(T), T...>::func(t);
}

int main()
{
	tuple<float, int, int> t(12.5f, 100, 52);
	func(t);
	return 0;
}

输出

12.5
100
52

核心代码,注意这里两个max_size

template <int max_size, typename ...T>
class A<max_size, max_size, T...>

每次调用的时候

A<index + 1, max_size, T...>::func(t);

当模板的前两个值相等时,就会调用偏特化版本函数。结束调用。

类型模板参数包的展开(基类参数包的展开)

通过模板参数给类A加了三个父类。。。又什么鬼操作

#include <iostream>
#include <map>
using namespace std;

template <typename ... T>
class A : public T...
{
public:
	A() : T()...
	{
		cout << "A() this = " << this << endl;
	}
};

class PA1
{
public:
	PA1()
	{
		cout << "PA1() this = " << this << endl;
	}
	int m_v;
};

class PA2
{
public:
	PA2()
	{
		cout << "PA2() this = " << this << endl;
	}
	int m_v;
};

class PA3
{
public:
	PA3()
	{
		cout << "PA3() this = " << this << endl;
	}
	int m_v;
};

int main()
{
	A<PA1, PA2, PA3> a;
	return 0;
}

输出

PA1() this = 00B4FEA8
PA2() this = 00B4FEAC
PA3() this = 00B4FEB0
A() this = 00B4FEA8

可变参模板的偏特化

不存在全特化。。。

#include <iostream>
#include <map>
using namespace std;

template <typename ...Args>
class A
{
public:
	A()
	{
		cout << "A() 泛化" << endl;
	}
};

template <typename First, typename ...Others>
class A <First, Others...>
{
public:
	A()
	{
		cout << "A() 偏特化 <First, Others...>" << endl;
	}
};

template <typename Arg>
class A <Arg>
{
public:
	A()
	{
		cout << "A() 偏特化 <Arg>" << endl;
	}
};

template <typename First, typename Second>
class A <First, Second>
{
public:
	A()
	{
		cout << "A() 偏特化 <First, Second>" << endl;
	}
};


int main()
{
	A<int> a;
	A<int, int> b;
	A<int, int, double> c;
	A<int, char, float, double> d;
	A e;
	return 0;
}

输出

A() 偏特化 <Arg>
A() 偏特化 <First, Second>
A() 偏特化 <First, Others...>
A() 偏特化 <First, Others...>
A() 泛化

多态在模板中的应用

#include <iostream>
#include <map>
using namespace std;

class Men
{
public:
	void eat()
	{
		cout << "men" << endl;
	}
};

class Women
{
public:
	void eat()
	{
		cout << "women" << endl;
	}
};

template<typename T>
void func(T& obj)
{
	obj.eat();
}


int main()
{
	Men men;
	Women women;
	func(men);
	func(women);
	return 0;
}

不需要父类,直接体现多态。有点像python的那种多态感觉了

模板的一些特殊继承关系

奇异奇特递归模板模式(CRTP)

在基类中使用派生类对象

#include <iostream>
#include <map>
using namespace std;

template <typename T>	// T代表的就是派生类
class Base
{
public:
	void asDerived()
	{
        // 派生类对象也是基类对象,所以这种静态类型转换没问题
		T & derived = static_cast<T&>(*this);	
        // 调用派生类的成员函数
		derived.func();							
	}

private:
	// 这两行是有特殊意义的
	Base() {};
	friend T;									// T派生类变成了友元类
};

class Derived1 : public Base<Derived1>
{
public:
	void func()
	{
		cout << "Derived1::func() called" << endl;
	}
};

template <typename T>
class Derived2 : public Base<Derived2<T>>
{

};

/*
这里的 Derived1 是错的,应该写Derived3,为了笔误会报错。才有了Base里面的

private:
	Base() {};
	friend T;									// T派生类变成了友元类


class Derived3 : public Base<Derived1>
{
public:
};

*/

int main()
{
	Derived1 d1;
	Derived2<int> d2;

	return 0;
}

其中

class Derived3 : public Base<Derived3>
{
public:
};

有一个问题是后面的 public Base<Derived3>可能会笔误写成其他的。

解决方法是在Base里面加私有构造函数,然后在加友元,听说在标准库有这种写法。

private:
	Base() {};
	friend T;									// T派生类变成了友元类

基于减少派生类中代码量的考虑

出发点是尽可能把一些代码挪到基类中,从而有效的减少派生类中的代码量

	template<typename T>
	struct shape
	{
		// 把派生类对象是否相等的判断挪到了基类中
        // (使用了在类模板中定义友元函数的手段把全局的operator==放到基类中)
		friend bool operator==(const shape<T>& obj1, const shape<T>& obj2) 
		{
			const T& objtmp1 = static_cast<const T&>(obj1);
			const T& objtmp2 = static_cast<const T&>(obj2);
			if (!(objtmp1 < objtmp2) && !(objtmp2 < objtmp1))
				return true;
			return false;
		}
	};

	struct square : public shape<square>
	{
		int sidelength; // 边长
	};

	//类外运算符重载
	bool operator < (square const& obj1, square const& obj2)
	{
		if (obj1.sidelength < obj2.sidelength)
		{
			return true;
		}
		return false;
	}

	/*template<typename T>
	bool operator==(const shape<T>& obj1, const shape<T>& obj2)
	{
		const T& objtmp1 = static_cast<const T&>(obj1);
		const T& objtmp2 = static_cast<const T&>(obj2);
		if (!(objtmp1 < objtmp2) && !(objtmp2 < objtmp1))
			return true;
		return false;
	}*/

基类调用派生类的接口与多态的体现(静态多态编程技术)

// 基类模板
template <typename T>
class Human
{
public:
	T& toChild()
	{
		return static_cast<T&>(*this);
	}
	void parentEat()
	{
		toChild().eat(); // 派生类给基类提供了调用接口
	}

private:
	Human() {};
	friend T;
};

// 子类
class Men :public Human<Men>
{
public:
	void eat()
	{
		cout << "man" << endl;
	}
};

// 子类
class Women :public Human<Women>
{
public:
	void eat()
	{
		cout << "woman" << endl;
	}
};

template<typename T>
void func(Human<T>& tmpobj)
{
	tmpobj.parenteat();
}

通过泛型实现多态。例子挺简单的。

混入 (Mixins)

一种编程手法,描述类与类的更新,像多重继承

基本案例

#include <iostream>
#include <map>
using namespace std;

template <typename...T>
class role : public T... //把传入的模板参数当做该类模板的父类
{
public:
	role() : T()..., m_attack(0.0), m_defence(0.0), m_life(100.0) {}
	role(double att, double def, double life) : T()..., m_attack(att), m_defence(def), m_life(life) {}
public:
	double m_attack; //攻击力
	double m_defence; //防御力
	double m_life;    //血量(生命值)
};

template <typename...T>
class family
{
public:
	vector< role<T...> > m_members;
	//....其他信息
};

struct npcattr
{
	int m_sort; //npc种类:0:代表装饰游戏场景的这种NPC,1:代表商人,卖服装。2:代表把游戏任务派送给玩家。
	std::string m_lang; //记录自言自语的一句话
};

struct playerattr
{
	int m_strength; //力量
	int m_agile;  //敏捷
	int m_constitution; //体质
};

using role_npc = role<npcattr>;
using role_player = role<playerattr>;
using role_mixnpc = role<npcattr, playerattr>; //通过混入技术方便的组合,自由的装配各种功能

using family_npc = family<npcattr>;

int main()
{


	return 0;
}
using role_npc = role<npcattr>;

原来可能是

class role_npc : public role
{
public:
	// 构造函数

public:
	npcattr m_strucattr;
};

可能出现的问题是,父类构造参数一变,子类的构造函数给的参数就要跟着改。非常不便。

使用混入后可避免

继承的解析又要偏特化递归了。。。好麻烦,先不写了

用参数化的方式表达成员函数的虚拟性

template <typename ... T>
class Base :public T...
{
public:
	void func()
	{
		cout << "Base::func()" << endl;
	}
};

如果T...里面的func是虚函数,那么func就是虚函数。

#include <iostream>
#include <map>
using namespace std;

template <typename ... T>
class Base :public T...
{
public:
	void func()
	{
		cout << "Base::func()" << endl;
	}
};

template <typename ... T>
class Derived :public Base<T...>
{
public:
	void func()
	{
		cout << "Derived::func()" << endl;
	}
};

class A
{

};

class AVir
{
public:
	virtual void func()
	{

	}
};

int main()
{
	Base<A> *b1 = new Derived<A>();
	b1->func();

	Base<AVir> *b2 = new Derived<AVir>();
	b2->func();
	return 0;
}

泛型参数的不同,导致了虚拟性的不同。。。


一般来说,一个类如果做父类,那么它应该有析构函数并且这个析构函数都应该是一个虚函数

什么情况下父类中可以没有析构函数或者析构函数可以不为虚函数:

(1) 子类并没有析构函数(不需要在析构函数中释放任何new出来的数据)

(2) 代码中不会出现父类指针指向子类对象(多态)的情形

为防止用父类指针new一个子类对象,可以把父类的析构函数用protected来修饰

可以参考Boost库中的noncopyable类

模板代码的组织结构与模板的显式实例化和声明

模板代码的组织结构

普通类:类定义和类实现要分别放在.h头文件和.cpp源文件中

编译项目时编译器会针对每个.cpp源文件作为一个编译单元来编译

类模板:不能在.h声明,在.cpp。实现。必须把模板写在.h里面,否则会编译错误。

如果多个.cpp源文件都实例化出来了MYClass<int>类,那么链接时编译器会选择其中一个MYClass<int>,其他的丢弃掉,这叫贪婪实例化。

类模板(函数模板)的定义和实现通常都放在.h头文件中,而不能把定义和实现代码分开

模板的显式实例化,模板声明,代码组织结构

通过显式 实例化 来避免这种生成多个相同类模板实例的开销。

每一个编译单元的 .cpp都会生成模板展开的类,应该项目可能很大,会有很多重复的类、

影响编译开销

显示实例化

template A<float>;	// 只写一次

其他的cpp文件

extern template A<float>;

当然模板函数也可以有类似的写法。

template void func();
extern template void func();

模板进阶

万能引用(universal reference / forwarding reference)

万能引用是一种类型

右值引用(全称:右值引用类型)是用&&符号表示。右值引用是绑定到右值上

万能引用离不开两种语境:

a) 必须是函数模板
b) 必须是发生了模板类型推断并且函数模板形参长这样:T&&

T&& 才是万能引用

下面的例子,不管传左值还是右值都可以过编译

如果实参传递了一个整型左值给形参,tmprv的类型最终会被推断为int &类型。

如果果实参传递了一个整型右值给形参,tmprv的类型最终会被推断为int &&类型

结论:T&&是一个万能 引用类型

template <typename T>
void func(T&& v)
{
	cout << "v = " << v << " " << typeid(v).name() << endl;
}

int main()
{
	int a = 10;
	func(a);
	func(10);
	return 0;
}

什么情形才是万能引用?

  • 一个是函数模板中用作函数参数的类型推断(参数中要涉及到类型推断),T&&

  • auto &&tmpvalue = ..... 也是万能引用

其他情况的&&,都是右值引用

万能引用资格的剥夺与辨认

剥夺 : const会剥夺 一个引用成为万能引用的资格,被打回原型成右值引用

也就是写成下面这样就错了

template <typename T>
void func(const T&& v)
{
	cout << "v = " << v << " " << typeid(v).name() << endl;
}

猜你喜欢

转载自www.cnblogs.com/Q1143316492/p/13179699.html