第七章 模板

7.6 模板特性

模板头文件包含函数模板和类模板成员函数的定义

通常我们调用一个函数时,编译器只需要掌握函数的声明。类似地,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。

模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此模板的头文件通常既包括声明,也包括定义。

模板实例化

1. 编译器在模板实例化时才生成代码

当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码,这一特性影响了我们如何组织代码以及错误何时被检测到。

Tips:需要注意的是,如果类模板的一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化,这一特性使得即使某种类型不能完全符合模板操作的要求,我们仍然能用该类型实例化类。

通常情况下编译器会在三个阶段报告错误:

  • 第一个阶段是编译模板本身时:编译器检查语法错误,例如忘记分号或者变量名拼错等
  • 第二个阶段是编译器遇到模板使用时:检查实参数目是否正确、参数类型是否匹配
  • 第三个阶段是模板实例化时:只有这个阶段才能发现类型相关的错误,依赖于编译器如何管理实例化,这类错误可能在链接时才报告
2. 控制实例化

当模板被使用时才会进行实例化这一特性意味着:相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中就都会有该模板的一个实例。

在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在C++11新标准中,我们可以通过显式实例化来避免这种开销。显式实例化有下面两种形式:

extern template declaration;  // 实例化声明
template declaration;         // 实例化定义

当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义:

// 这些模板类型必须在程序其他位置进行实例化
extern template class Foo<string>;
extern template int compare(const int&, const int&);

实例化文件必须为每个在其他文件中声明为extern的类型和函数提供一个(非extern)的定义:

// 实例化compare的int版本
template int compare(const int&, const int&);
// 实例化类模板的所有成员
template class Foo<string>;
3. 类模板的实例化定义会实例化所有成员

一个类模板的实例化定义会实例化该模板的所有成员,包括内敛的成员函数。当编译器遇到一个实例化定义时,他不了解程序使用哪些成员函数,因此与处理类模板的普通实例化不同,编译器会实例化该类的所有成员。即使我们不使用某个成员,它也会被实例化,因此显式实例化一个类模板时必须能用于模板的所有成员。

模板特例化

1. 简介

编写单一模板,使之对任何可能的模板实参都是最合适的,都能实例化,这并不总是能办到。当我们不能(或不希望)使用模板版本时,可以定义类或函数模板的一个特例化版本。

2. 函数模板特例化

举个例子,我们构造一个compare函数用于比较不同类型的大小,并重载了compare函数来处理字符串字面常量:

// 函数模板一: 可以比较任意两个类型
template <typename T> int compare(const T&, const T&);
// 函数模板二: 处理字符串字面常量
template <size_t N, size_t M>
int compare(const char(&)[N], const char(&)[M]);

需要注意的是,只有当我们传递给compare一个字符串字面常量或者一个数组时,编译器才会调用接收两个非类型模板参数的版本。如果我们传递给它字符指针,它就会调用第一个版本(因为我们无法将一个指针转换成一个数组的引用):

const char *p1 = "tomo", *p2 = "cat";
compare(p1, p2);         // 调用第一个模板
compare("tomo", "cat");  // 调用第二个模板

Tips:当定义函数模板的特例化版本时,我们本质上接管了编译器的工作,即我们为原模板的一个特殊实例提供了定义。

为了处理字符指针(而不是数组),可以为第一个版本的compare定义一个模板特例化版本。当我们特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板,应该使用关键字template后跟一个空尖括号对<>

// 函数模板
template <typename T>
int compare(const T&, const T&);
// compare的特殊版本, 处理字符数组的指针
template <>
int compare(const char* const &p1, const char* const &p2) {
    return strcmp(p1, p2);
}

需要注意的是,一个特例化版本本质上是一个实例,而非函数名的一个重载版本。如果我们将接收字符指针的compare版本定义为一个普通的非模板函数(而不是函数模板的一个特例化版本),此调用的解析就会不同。在此情况下,将由三个可行的函数:两个模板和非模板的字符指针版本。

3. 类模板特例化

作为一个例子,我们为标准库hash模板定义一个特例化,可以用它来将Foo对象保存在无序容器中。默认情况下,无序容器使用hash<key_type>来组织其元素,为了让我们的数据类型也能使用这种默认组织方式,必须定义hash模板的一个特例化版本。一个hash类必须定义:

  • 一个重载的调用运算符,它接受一个容器关键字类型的对象,返回一个size_t
  • 两个类型成员,result_typeargument_type,分别是调用运算符的返回类型和参数类型
  • 默认构造函数和拷贝赋值运算符(可以隐式定义)

在定义此特例化版本的hash时,唯一复杂的地方是:必须在原模板定义所在的命名空间中特例化它。为了达到这一目的,首先必须打开命名空间:

struct Foo {
    string str;
    double d;
};

// 打开std命名空间, 以便特例化std::hash
namespace std {

template <>  // 我们正在定义一个特例化版本, 模板参数为Foo
struct hash<Foo> {
    // 用来散列一个无序容器的类型必须要定义下列类型
    typedef size_t result_type;
    typedef Foo argument_type;
    size_t operator()(const Foo& foo) const;
    // 我们的类使用合成的拷贝控制成员和默认构造函数
};

size_t hash<Foo>::operator()(const Foo& foo) const {
    return hash<string>()(foo.str) ^
           hash<double>()(s.d);
}

}  // 关闭std命名空间, 注意右花括号之后没有分号

Tips:为了让Foo的用户能使用hash特例化版本,我们应该在Foo的头文件中定义该特例化版本。

4. 类模板部分特例化

Tips:我们只能部分特例化类模板,而不能部分特例化函数模板。

与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数,或是参数的一部分而非全部特性。一个类模板的部分特例化本身是一个模板,使用它时用户还必须为那些在特例化版本中未指出的模板参数提供实参。

举个例子,标准库remove_reference类型是一个模板类,它是通过一系列的特例化版本来完成其功能的:

// 原始的、最通用的版本
template <class T> struct remove_reference {
    typedef T type;
};
// 部分特例化版本, 将用于左值引用和右值引用
template <class T> struct remove_reference<T&> {
    typedef T type;
};
template <class T> struct remove_reference<T&&> {
    typedef T type;
};

int i;
// decltype(42)为int, 使用原始模板
remove_reference<decltype(42)>::type a;
// decltype(i)为int&, 使用第一个(即T&)部分特例化版本
remove_reference<decltype(i)>::type b;
// decltype(std::move(i))为int&&, 使用第二个(即T&&)部分特例化版本
remove_reference<decltype(std::move(i))>::type c;
5. 特例化成员函数

我们可以只特例化特定成员而不是特例化整个模板。例如对于一个包含bar()成员模板类Foo,我们可以只特例化该bar()成员:

template <typename T> struct Foo {
  	Foo(const T &t = T()) : mem(t) { }
    void Bar() { /*...*/ }
    T mem;
};

// 特例化Foo<int>的Bar()成员
template <>
void Foo<int>::Bar() {
    // 进行应用于int的特例化处理
}

Foo<string> fs;  // 实例化Foo<string>::Foo()
fs.Bar();        // 实例化Foo<string>::Bar()
Foo<int> fi;     // 实例化Foo<int>::Foo()
fi.Bar();        // 使用我们特例化版本的Foo<int>::Bar()
  • 29
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: C++ 标准模板库(STL)是一个强大的工具,为程序员提供了许多可以直接使用的容器、算法和迭代器。《C++ 标准模板库编程实战》这本书集中讲解了 STL 的使用方法和实战技巧,旨在帮助读者开发出高效且易于维护的 C++ 程序。 这本书共分为四个部分。第一部分介绍了 STL 的基础知识,主要包括容器、迭代器、算法、函数对象等内容。第二部分重点讲解了序列容器和关联容器,以及它们常见的应用。第三部分主要是算法,详细介绍了 STL 中常用的算法,并且通过实例演示了其使用方法。第四部分主要是 STL 的高级应用,如智能指针、异常处理、多线程等。 此外,这本书还提供了大量的实战案例,这些案例既包括独立的小应用程序,也包括较为完整的项目代码。通过这些案例,读者可以深入了解 STL 的使用和设计思路,并掌握一些实用的编程技巧。 总的来说,这本《C++ 标准模板库编程实战》是一本非常实用的书籍,不仅适合初学者入门,也适合有一定经验的开发者进一步提高自己的编程技能。建议读者在学习这本书时,可以边读边动手实践,更好地理解和掌握其中的内容。 ### 回答2: c++标准模板库编程实战是一本非常经典、详实的c++ STL实战教材,主要讲解了STL的各种容器、算法和迭代器的常用操作和实现原理,并且通过大量的实例演示了STL在真实项目中的实际应用。 本书总共分为10个章节,前两章是介绍STL的基础知识和核心组件,包括迭代器、容器、算法等;第三章是介绍序列容器,主要包括vector、list、deque、stack、queue、heap、priority_queue和bitset等;第四章是介绍关联容器,主要包括set、multiset、map、multimap等;第五章是介绍迭代器,包括迭代器分类,迭代器实现方式和应用场景等;第六章是介绍函数对象,包括函数对象的定义、STL内置函数对象、自定义函数对象和函数对象适配器等;第七章是介绍算法基础,包括常用算法和自定义算法的实现;第八章是介绍字符串,在字符串操作方面,STL提供了string和wstring类,以及一些与之相关的算法;第九章是介绍STL的高级用法,包括元编程、策略模式、继承体系、嵌套类和allocator等;第十章是介绍STL和相关技术的未来发展趋势和发展方向。 总的来说,c++标准模板库编程实战是一本非常好的STL实战教材,既可以作为初学者入门的指南,也可以作为中高级程序员巩固和深入学习STL的参考书。无论是学习STL的基础知识、习惯性使用STL容器和算法,还是在项目中灵活高效地应用STL,都会受益匪浅。 ### 回答3: c标准模板库(STL)是一组C++模板类和函数的集合,可以让程序员使用一些高效的算法数据结构,从而降低了开发者的工作量,提高了C++程序的效率和可维护性。 《C++标准模板库编程实战》是一本介绍STL的经典教材,全书共分为25个章节,内容涉及到STL的迭代器、算法、容器、函数对象、适配器等方面。可谓是STL入门的重要读物。 该书的编写思路以工程实践为导向,讲解一些常用的数据结构算法的实现过程,并给出了一些标准库中经典的函数的代码实现。例如,生成随机数的代码、字符串排序的代码、实现二叉堆的代码等等。这些代码可以帮助开发者更好地理解STL中的模板类和函数的实现原理和效率。 此外,该书对STL的算法进行了详细介绍,包括容器、迭代器、函数对象等方面的应用。为了方便程序员,书中还提供了一些实用的STL程序库的代码,例如STL的多个容器和关联式容器,还有STL库中提供的适配器库等。 总之,《C++标准模板库编程实战》是学习STL的必备参考书,不仅深入浅出地讲解了STL的实现原理和应用,更是教会了我们如何将STL运用到工程中,将编程变得更加高效和简单。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值