C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

模板与泛型编程

--模板特化

引言:

我们并不总是能够写出对全部可能被实例化的类型都最合适的模板。某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一些情况下,能够利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数。

compare函数和 Queue类都是这一问题的好样例:与C风格字符串一起使用进,它们都不能正确工作。

compare函数模板:

template <typename Type>
int compare(const Type &v1,const Type &v2)
{
    if (v1 < v2)
        return -1;
    if (v2 < v1)
        return 1;
    return 0;
}

假设用两个constchar*实參调用这个模板定义,函数将比較指针值。它将告诉我们这两个指针在内存中的相对位置,但没有说明与指针所指数组的内容有关的不论什么事情

为了能够将compare函数用于字符串,必须提供一个知道如何比較C风格字符串的特殊定义。这些版本号是特化的,这一事实对模板的用户透明。对用户而言,调用特化函数或使用特化类,与使用从通用模板实例化的版本号无法差别。

一、函数模板的特化

模板特化:该定义中一个或多个模板实參的实际类型或实际值是指定的。特化形式例如以下:

1)keywordtemplate后面接一对空的尖括号(<>);

2)再接模板名和一对尖括号,尖括号里指定这个特化定义的模板形參;

3)函数形參表;

4)函数体。

当模板形參类型绑定到const char* 时,compare函数的特化:

template<>
int compare<const char *>(const char *const &v1,
                          const char *const &v2)
{
    return strcmp(v1,v2);
}

特化的声明必须与相应的模板相匹配。当调用compare函数的时候,传给它两个字符指针,编译器将调用特化版本号。编译器将为随意其它实參类型(包括普通char*)调用泛型版本号:

    const char *p1 = "Hello",*p2 = "world";
    int i1,i2;
    cout << compare(p1,p2) << endl;
    cout << compare(i1,i2) << endl;

1、声明模板特化

与随意函数一样,函数模板特化能够声明而无须定义

template<>
int compare<const char *>(const char *const &v1,
                          const char *const &v2);

模板特化必须总是包括空模板形參说明符,即template<>,并且,还必须包括函数形參表。假设能够从函数形參表判断模板实參,则不必显式指定模板实參:

int compare<const char *>(const char *const &v1,
                          const char *const &v2);	//Error

template<>
int compare<const char *>;	//OK

template<>
int compare(const char *const &v1,
            const char *const &v2);	//OK

2、函数重载与模板特化

在特化中省略空的模板形參表template<>会有令人吃惊的结果。假设缺少该特化语法,则结果是声明该函数的重载非模板版本号:

template<>
int compare(const char *const &v1,
            const char *const &v2);

int compare(const char *const &v1,
            const char *const &v2);	//OK:可是是非模板的重载版本号!

当定义非模板函数的时候,对实參应用常规转换;当特化模板的时候,对实參类型不应用转换。在模板特化版本号的调用中,实參类型必须与特化版本号函数的形參类型全然匹配,假设不全然匹配,编译器将为实參从模板定义实例化一个实例。

3、不是总能检測到反复定义

假设程序由多个文件构成,模板特化的声明必须在使用该特化的每一个文件里出现。不能在一些文件里从泛化模板定义实例化一个函数模板而在其它文件里为同一个模板实參集合特化该函数版本号

【最佳实践】

与其它函数声明一样,应在一个头文件里包括模板特化的声明,然后使用该特化的每一个源文件包括该文件!

4、普通作用域规则用于特化

在能够声明或定义特化之前,它所特化的模板的声明必须在作用域中。类似的,在调用模板的这个版本号之前,特化的声明必须在作用域中:

template <typename Type>
int compare(const Type &v1,const Type &v2)
{
    cout << "generic" << endl;
    if (v1 < v2)
        return -1;
    if (v2 < v1)
        return 1;
    return 0;
}

int main()
{
    int i = compare("Hello","World");	//print “generic”
}

template<>
int compare<const char *>(const char *const &v1,
                          const char *const &v2)
{
    cout << "hah" << endl;
    return strcmp(v1,v2);
}

这个程序有错误,由于在声明特化之前,进行了能够与特化相匹配的一个调用。当编译器看到一个函数调用时,它必须知道这个版本号须要特化,否则,编译器将可能从模板定义实例化该函数。

对具有同一模板实參集的同一模板,程序不能既有显式特化又有实例化。

特化出如今对该模板实例的调用之后是错误的

//P567 习题16.52/53/54
template <typename ValType>
size_t count(const vector<ValType> &vec,const ValType &val)
{
    size_t appers = 0;
    for (typename vector<ValType>::const_iterator
            iter = vec.begin();
            iter != vec.end(); ++iter)
    {
        if (*iter == val)
        {
            ++ appers;
        }
    }

    return appers;
}

template <>
size_t count(const vector<string> &vec,const string &val);

int main()
{
    ifstream inFile("input");
    vector<int> ivec;
    int val;
    while (inFile >> val)
    {
        ivec.push_back(val);
    }

    while (cin >> val)
    {
        cout << count(ivec,val) << endl;
    }
}

template <>
size_t count(const vector<string> &vec,const string &val)
{
    size_t appers = 0;
    for (typename vector<string>::const_iterator
            iter = vec.begin();
            iter != vec.end(); ++iter)
    {
        if (*iter == val)
        {
            ++ appers;
        }
    }

    return appers;
}

二、类模板的特化

当用于C风格字符串时,Queue类具有与compare函数类似的问题。在这样的情况下,问题出在push函数中,该函数复制给定值以创建Queue中的新元素。默认情况下,复制C风格字符串仅仅会复制指针,不会复制字符。这样的情况下复制指针将出现共享指针在其它环境中会出现的全部问题,最严重的是,假设指针指向动态内存,用户就有可能删除指针所指的数组

1、定义类特化

为C风格字符串的Queue提供正确行为的一种途径,是为constchar *定义整个类的特化版本号:

template<> class Queue<const char *>
{
public:
    void push(const char *);
    void pop()
    {
        real_queue.pop();
    }
    bool empty() const
    {
        return real_queue.empty();
    }
    std::string front()
    {
        return real_queue.front();
    }
    const std::string front() const
    {
        return real_queue.front();
    }

private:
    Queue<std::string> real_queue;
};

这个实现了Queue一个数据元素:string对象的Queue。各个成员将它们的工作委派给这个成员

Queue类的这个版本号未定义复制控制成员,它唯一的数据成员为类类型,该类类型在被复制、被赋值或被撤销时完毕正确的工作。能够使用合成的复制控制成员

这个Queue类实现了与Queue的模板版本号大部分同样但不全然同样的接口,差别在于front成员返回的是string而不是char*,这样做是为了避免必须管理字符数组——假设想要返回指针,就须要字符数组。

值得注意的是:特化能够定义与模板本身全然不同的成员。假设一个特化无法从模板定义某个成员,该特化类型的对象就不能使用该成员类模板成员的定义不会用于创建显式特化成员的定义。

【最佳实践】

类模板特化应该与它所特化的模板定义同样的接口,否则当用户试图使用未定义的成员时会感到奇怪。


2类特化定义

在类特化外部定义成员时,成员之前不能加template<>标记。

void Queue<const char *>::push(const char *p)
{
    return real_queue.push(p);
}

尽管这个函数差点儿没有做什么工作,但它隐式复制了val指向的字符数组。复制是在对real_queue.push的调用中进行的,该调用从const char * 实參创建了一个新的string对象。const char  * 实參使用了以const char  * 为參数的string构造函数,string构造函数将val所指的数组中的字符拷贝到未命名的string对象,该对象将被存储在push到 real_queue的元素中。

C++ Primer 学习笔记_84_模板与泛型编程 --模板特化,布布扣,bubuko.com

时间: 2024-12-25 06:31:03

C++ Primer 学习笔记_84_模板与泛型编程 --模板特化的相关文章

C++ Primer 学习笔记_77_模板与泛型编程 --实例化

模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化. 1.类的实例化 当编写Queue<int>qi时,编译器自己主动创建名为Queue<int>的类.实际上,编译器通过又一次编写Queue模板,用类型int取代模板形參的每次出现而创建Queue<int

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

模板与泛型编程 --类模板成员[续1] 二.非类型形参的模板实参 template <int hi,int wid> class Screen { public: Screen():screen(hi * wid,'#'), cursor(hi * wid),height(hi),width(wid) {} //.. private: std::string screen; std::string::size_type cursor; std::string::size_type height

C++ Primer 学习笔记_82_模板与泛型编程 --类模板成员[续2]

模板与泛型编程 --类模板成员[续2] 六.完整的Queue类 Queue的完整定义: template <typename Type> class Queue; template <typename Type> ostream &operator<<(ostream &,const Queue<Type> &); template <typename Type> class QueueItem { friend clas

C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模板的定义. 泛型编程与面向对象编程一样,都依赖于某种形式的多态性.面向对象编程中的多态性在执行时应用于存在继承关系的类.我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异.仅仅要使用基类的引用或指针,基类类型或派生类类型的对象就能够使用同样的代码. 在泛型编程中,我们所编写的类和函数能够

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程 --一个泛型句柄类 引言: [小心地雷] 这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板.在熟悉了这些特性之后再研究这个例子也许会帮助.另一方面,这个例子还能很好地测试你对这些特性的理解程度. 前面示例的Sales_item和Query两个类的使用计数的实现是相同的.这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数.原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化.至于是公开还是隐藏下

C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还可以只特化push和pop成员.我们将特化push成员以复制字符数组,并且特化pop成员以释放该副本使用的内存: template<> void Queue<const char *>::push(const char *const &val) { char *new_item = new char[sizeof(val) + 1]; strncpy(new_item,val,sizeof(

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不立即产生代码.只有在用到模板时,如果调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调用函数时[不是模板],编译器只需看到函数的声明.类似的,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的.因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中. 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码.当调用函数模板或类模板的成员函

C++ Primer 学习笔记_80_模板与泛型编程 --类模板成员

模板与泛型编程 --类模板成员 引言: 这一节我们介绍怎样实现前面提到的Queue模板类. 标准库将queue实现为其他容器之上的适配器.为了强调在使用低级数据结构中设计的编程要点,我们将Queue实现为链表.实际上,在我们的实现中使用标准库可能是个更好的决定!!-_-. 1.Queue的实现策略 如图所示,我们实现两个类: 1)QueueItem类表示Queue的链表中的节点,该类有两个数据成员item和next: a. item保存Queue中元素的值,它的类型随Queue的每个实例而变化:

C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

模板与泛型编程 --重载与函数模板 引言: 函数模板可以重载:可以定义有相同名字但参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数. 但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性. 一.函数匹配与函数模板 如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下: 1.为这个函数名建立候选函数集合,包括: a.与被调用函数名字相同的任意普通函数. b.任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配