特化与重载

现在我们已经知道如何使一个泛型定义扩展成一些相关的类家族和函数家族。但是有些时候,我们需要同一个名字的函数的不同实现(透明定义),为的是在不同情况下获得较高的性能,在这种情况下就不是简单的参数替换就能够解决的。

一、透明定义使用的技术

对于函数模板使用的透明定义的技术是:函数模板的重载

对于类模板的使用透明定义的技术是:类模板的特化(全局特化和局部特化)

二、重载函数模板

两个名称相同的模板可以同时存在,还可以对他们进行实例化。

template<typename T>

int f(T)

{

retrun 1;

}

template<typename T>   //重载版本

int f(T*)

{

return 2;

}

如果我们用int*来替换第一个模板参数T,用int来替换第二个模板参数T,纳闷将会获得两个具有相同参数类型和返回类型的同名函数。

int main()

{

cout << f<int*>((int*)0) << endl;  //这里回实例化出两个实体,根据实参(int*)选择接近的

cout << f<int>((int*)0) << endl;  //这里会实例化出两个实体,根据实参(int*)选择接近的

}

注意:表达式0是一个整数,而不是一个null指针常量。只有在发生特定的隐式转型之后参会成为一个null的指针常量,但是在模板实参演绎的过程中并不会考虑这种转型。

三、签名

只要具有不同的签名,就可以在同一个程序中存在,注意是同一个程序而不是同一个翻译单元

对函数签名的定义如下:

(1)非受限的函数名称

(2)函数名臣各所属的额类的作用域获名字空间作用域,如果函数名称具有内部连接属性还包括名称声明所在的翻译单元

(3)函数const,volatile或者是cons volatile限定符

(4)函数的参数类型(如果是产生自模板的,指的是模板参数被替换之前的类型)

(5)如果这个函数产生自函数模板,那么还包括它的返回值类型

(6)如果这个函数是产生自函数模板,那么包括模板参数,模板实参。

template<typename T1, typename T2>

void f1(T1, T2);

template<typename T1, typename T2>

void f2(T2, T1);

template<typename T>

long f2(T);

template<typename T>

char f2(T);

如果上面的这些模板是在同一个作用域中进行声明的话,我们不能使用某些模板,因为实例化过程会导致二义性    

#include<iostream>

template<typename T1, typename T2>

void f1(T1, T2)

{

std::cout << "f1(T1, T2)\n";

}

template<typename T1, typename T2>

void f1(T2, T1)

{

std::cout << "f1(T2, T1)\n";

}

int main()

{

f1<char, char>(‘a‘, ‘b‘); //错误:二义性

}

因此只有在这两个模板出现不同的翻译单元,它们两个实例化体才可以在同个程序同时存在。

//翻译单元1

#include <iostream>

template<typename T1, typename T2>

void f1(T1, T2)

{

std::cout << "f1(T1, T2)\n" ;

}

void g()

{

f<char, char>(‘a‘, ‘b‘);

}

//翻译单元2

#include<iostream>

template<typename T1, typename T2>

void f1(T2, T1)

{

std::cout << "f1(T2, T1)\n";

}

extern void g(); //定义在单元1

int main()

{

f<cahr, char>(‘a‘, ‘b‘);

g();

}

最后输出:

f1(T2, T1);

f1(T1, T2);

四、显示特化类模板

C++标准的“显示特化”概念指的是一种语言特性,我们通常称之为全局特化。

它为类模板提供了一种使模板参数可以被全局替换的实现,而没有剩下的模板参数。事实上,类模板,函数模板都可以被全局特化,而类模板的成员(成员函数, 嵌入类, 静态成员变量等,他们的定义可以位于类定义的外部)也可以被全局特化。

(一)如何显示特化

template<typename T>

class S{

public:

void f(){

std::cout << "generic(S<T>::info())" << std::endl;

}

};

template<> //显示特化

class S<void>{

public:

void msg(){                     //新增加了msg函数

std::cout << "fully specialized (S<void>::msg())" << std::endl;

}

}

实际上,全局特化之和类模板的名称关联,可以包含名称不同的成员函数。

(二)模板特化声明

(模板)全局特化的声明不一定是定义。当一个全局特化声明之后,针对该特化的模板实参列表调用,将不再使用模板的泛型定义,而是使用这个特化的定义。所以如果在一个调用处需要该特化的定义,而在这之前没有提供定义(光有声明)那么程序将会出现错误。

template<typename T>

class Types{

public:

typedef int I;

};

template<typename T, typename U = typename Types<T>::I>//注意这里有默认形参

class S;    //(1)

template<>

class S<void>{      //(2)

public:

void f();

};

template<>class S<char, char>;   //(3)

template<> class S<char, 0>; //错误应该是一个类型

int main()

{

S<int>* p1; //使用(1)处声明, 不需要定义

S<int>  e1; //使用(1)处声明, 需要定义,但是找不到定义

S<void> pv; //使用(2)处声明

S<void, int> sv;//使用(2)处声明, 使用默认形参

S<void, char> e2; //使用(1)处声明, 需要找到定义,但是找不到定义

S<char, cahr> e3; //使用(3)处声明,需要定义,但是找不到定义

}

template<>

class S<char, char>{};  //(3)处的定义

(三)如何定义全局模板特化成员

对于特化声明而言,因为它并不是模板声明,所以应该使用普通的成员定义语法,来定义全局类模板特化的成员(即不能使template<>前缀)

template<typename T>

class S;

template<> class S<char**> {

public:

void print() const;

}

void S<char**>::print() const //这里不能使用template<>

{

std::cout << "使用普通成员定义语法来定义全局类模板特化成员函数" << std::endl;

}

(四)全局特化版本与实例化版本不能共同存在一个程序中(即使是在不同的翻译单元)

template<typename T>

class Invalid{};

Invalid<double> x1; //此处将产生一个Invalid<double>实例化体

template<>

class Invalid<double>; //错误:Invalid<double>已经被实例化了

(五)特化全局成员函数

template<typename T>   //(1)泛型模板定义

class Outer{

public:

template<typename U>

class Inner{

private:

static int count;

};

static int code;  //(4)

coid print() const{  //(5)

std::cout << "generic" << std::endl;

}

};

template<tyepname T>

int Outer<T>::code = 6; //泛型模板静态成员定义

template<typename T> template<typename U>

int Outer<T>::Inner<U>::count = 7;// 泛型模板静态成员定义

template<>

int Outer<void>::count = 12; //特化全局成员函数

template<>

int Outer<void>::print() const

{

std::cout << “Outer<void>” << std::endl;

}

定义将会代替类Outer<void>(4)和(5)的泛型定义, 类Outer<void>的其他成员仍然默认的按照(1)处的泛型模板定义产生。另外由于这样也算全局特化了Outer<void>,所以不能再次提供Outer<void>的显式特化。

(六)template<>不能位于template<类型>后面,也就是说,不能先特化Inner,而不特化Outer。

 

五、局部的类模板特化

因为该模板定义所使用的模板实参只是被局部的指定所以被称为局部特化。

(一)怎样个局部指定法?

template<int N1, int N2>

class S{}; //(1)

template<2, 3> //全局特化

class S{};

template<int N> //(2)局部特化,改模板的实参只是被局部指定。

class S<2, N>{};

这样就把类模板特化成一个“针对模板实参”的类家族,上面的例子就是针对第一个模板实参为2的类家族。

(1)处叫做基本模板。(2)处叫做局部特化

-------------------------------------------------------------

template<typename T>

class List{

public:

void append(T const&);

inline size_t length() const;

};

template<>  //全局特化

class List<void*>{

public:

void append(void* const&);

inline size_t length() const;

};

template<typename T> //局部特化

class List<T*>{

private:

List<void*> impl;

public:

void append(T* p){

impl.append(p);

}

size_t length() const{

return impl.length();

}

};

上面的例子就是针对模板实参为指针的类家族,并且这里使用了一点小技巧:该特化使用了“委托代理”,将所有该类家族的处理交由List<void*>实例处理。这样纸就解决了C++模板备受指责的代码膨胀问题。

为此上面的例子还要一个List<void*>的全局特化,以便编译器可以找到该实例。

template<>

class List<void*>{

void append(void* p);

inline size_t length() const;

};

针对局部特化有以下的一些约束条件:

(1)局部特化的参数必须和基本模板参数在种类上相匹配

(2)局部特化的参数列表不能有缺省实参,但局部特化仍然可以使用基本模板的缺省实参

(3)局部特化的实参或是模板参数,不能是更复杂的依赖型表达式,(诸如2*N,其中N是模板参数)对于非类型实参,或是普通的非类型模板参数。

(4)局部特化的模板实参列表不能和基本模板的参数列表完全相同。(不考虑重新命名)

template<typename T, int I = 3>

class S; //基本模板

template<typename T>

class S<int, T> //错误,第二个参数要是int类型的值,参数类型不匹配。

template<typename T = int>

class S<T, 10> //错误,不能有缺省实参

template<int I>

class S<int, I*2> //错误,局部特化不能有表达式

template<typename U int R>

class S<U, R> //错误,跟基本模板没有实质上区别

编辑整理:Claruarius,转载请注明出处。

时间: 2024-10-18 00:13:44

特化与重载的相关文章

template_12特化与重载

1,重载函数模板f<int*>((int*)pi);//1f<int>((int*)pi);//2上面用int*替换第一个模板的T,用int来替换第二个模板的T.那么将得到两个相同参数类型(int*)的同名函数.也就是不仅同名模板可以同时存在,它们各自具有相同参数类型和返回类型的实例化体也可以同时存在. f(pi);对于这两个模板实参演绎都可以获得成功,即f<int*>(int*)和f<int>(int*).这也就意味着调用是二义性的.但是,考虑重载解析的额

C++普通函数与模板函数以及特化函数重载的优先级问题

在面对C++模板的时候,需要十分注意,因为模板的复杂性有很多情况,所以最好学习模板的方法我个人认为就是用到就去学,用不到就尽量别去看各种奇门怪技,因为你就算看了,好不容易搞懂模板的实现内部了,包括元编程啊什么的,但真正用到工作中的我相信很少,不久你也会忘掉,所以,对于模板,我们可以采取用到哪学到哪的观念去学习,这样可以节省时间并且让效率最大化. 今天主要讲在关于模板特化已经函数重载的问题,简单举下例子 1 void say(int value); 2 template <typename T>

函数模板示例(三) 模板函数的特化与重载

function3.h中的代码: #ifndef FUNCTION3_H #define FUNCTION3_H #include <string> #include <cstring> #include <iostream> template <typename T> T myMax(const T p1, const T p2) { std::cout << "template default func" <<

C++ 模板编程 - 第十二章 特化与重载

对程序效率的考虑 template<typename T> inline void swap(T *a, T * b) { T tmp(*a); *a = *b; *b = tmp; } 上面的代码实际上进行了三次拷贝,如果类型的尺寸比较大,显然会很浪费时间.解决方案是可以给特定的类型定义更高效的用于交换的成员函数——其实这不是重点,重点是我之前从来没有考虑过这个问题!

C++—模板(2)类模板与其特化

我们以顺序表为例来说明,普通顺序表的定义如下: 1 typedef int DataType; 2 //typedef char DataType; 3 class SeqList 4 { 5 private : 6 DataType* _data ; 7 int _size ; 8 int _capacity ; 9 } ; 模板类也是模板, 必须以 关键字templ ate开头, 后接模板形参表. 模板类一般格式如下:template<class 形参名 1, class 形参名 2, .

C++ template —— 模板特化(五)

本篇讲解模板特化------------------------------------------------------------------------------------------------------------第12章 特化和重载------------------------------------------------------------------------------------------------------------前面几篇博客讲解了C++模板如何

为什么不要特化函数模版?

/* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm;

C++ 模板应用浅析

把曾经写的C++模板的应用心得发表出来. 回忆起当时在学习C++模板时的无助和恐惧,如今还心有余悸.我分享出来我的心得,仅仅希望别人少走弯路,事实上它就这么几种使用方法,不须要害怕. 我总结了模板的四种使用方式,基本覆盖了大部分的模板使用场景,了解了这四种方式.就能够在看其他代码时理解别人为什么会在这个地方用模板. 模板的四大场景 1.数据类型与算法相分离的泛型编程 2.类型适配Traits 3.函数转发 4.元编程 1.数据类型与算法相分离的泛型编程 在模板编程中,最常见的就是此类使用方法.将

Effective C++ 条款25 考虑写出一个不抛出异常的swap函数

1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下: namespace std{ template<typename T> swap(T& lhs,T& rhs){ T temp(lhs); lhs=rhs; rhs=temp; } } 只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某