模板实例化与名称绑定详解
立即解锁
发布时间: 2025-08-22 00:56:35 阅读量: 2 订阅数: 8 


C++编程语言精髓与实践
### 模板实例化与名称绑定详解
#### 1. 引言
模板是一种极其灵活的代码组合机制。编译器为了生成高质量的代码,会结合模板定义及其词法环境、模板参数及其词法环境,还有模板使用的环境等多方面的信息。然而,模板定义中的代码往往不像我们期望的那样具有很强的局部性,这就导致我们有时会对模板定义中使用的名称所指的内容感到困惑,比如它可能是局部名称、与模板参数关联的名称、来自基类的名称、命名空间中的名称或者全局名称等。
#### 2. 模板实例化
给定一个模板定义和对该模板的使用,编译器的任务是生成正确的代码。从类模板和一组模板参数,编译器需要生成类的定义以及程序中使用到的成员函数的定义(仅生成使用到的);从模板函数和一组模板参数,需要生成相应的函数。这个过程就是模板实例化。
生成的类和函数被称为特化。为了区分编译器生成的特化和程序员显式编写的特化,我们分别称之为生成特化和显式特化,显式特化也常被称为用户定义特化或简称用户特化。
在非平凡的程序中使用模板时,程序员必须理解模板定义中使用的名称如何与声明绑定,以及如何组织源代码。默认情况下,编译器会根据名称绑定规则从使用的模板生成类和函数,程序员无需明确指定要生成哪些模板的哪些版本。不过,有时程序员需要明确指定从模板生成代码的位置,这样可以对实例化的上下文进行精细控制。
##### 2.1 何时需要实例化
只有在需要类的定义时,才需要生成类模板的特化。例如,声明一个指向类的指针时,不需要类的实际定义:
```cpp
class X;
X* p; // OK: no definition of X needed
X a; // error : definition of X needed
```
在定义模板类时,这种区别很关键。模板类只有在实际需要其定义时才会被实例化:
```cpp
template<typename T>
class Link {
Link* suc; // OK: no definition of Link needed (yet)
// ...
};
Link<int>* pl; // no instantiation of Link<int> needed (yet)
Link<int> lnk; // now we need to instantiate Link<int>
```
模板的使用位置定义了一个实例化点。
编译器只有在模板函数被使用(调用或取其地址)时才会实例化该函数。特别地,类模板的实例化并不意味着其所有成员函数都会被实例化,这为程序员在定义模板类时提供了很大的灵活性。例如:
```cpp
template<typename T>
class List {
// ...
void sort();
};
class Glob {
// ... no comparison operators ...
};
void f(List<Glob>& lb, List<string>& ls)
{
ls.sort();
// ... use operations on lb, but not lb.sort() ...
}
```
在上述代码中,`List<string>::sort()` 会被实例化,而 `List<Glob>::sort()` 不会,这既减少了生成的代码量,又避免了重新设计程序。
##### 2.2 手动控制实例化
语言虽然不要求用户进行显式操作来实现模板实例化,但提供了两种机制帮助用户在需要时进行控制,其需求通常源于优化编译和链接过程、消除冗余实例化,或者明确知道使用哪个实例化点以避免复杂名称绑定上下文带来的意外情况。
显式实例化请求是在特化声明前加上关键字 `template`(后面不跟 `<`):
```cpp
template class vector<int>; // class
template int& vector<int>::operator[](int); // member function
template int convert<int,double>(double); // nonmember function
```
模板声明以 `template<` 开头,而单纯的 `template` 则开始一个实例化请求。需要注意的是,`template` 前缀必须是一个完整的声明,仅声明名称是不够的。
当类模板被显式实例化时,其每个成员函数也会被实例化。实例化请求对链接时间和重新编译效率的影响可能很大。同时,为了避免同一个特化有多个定义的错误,语言还提供了显式请求不实例化(通常称为 `extern` 模板),其用法类似于经典的一个定义和多个声明的使用方式。例如:
```cpp
#include "MyVector.h"
extern template class MyVector<int>; // suppresses implicit instantiation
// explicitly instantiate elsewhere
void foo(MyVector<int>& v)
{
// ... use the vector in here ...
}
```
“elsewhere” 可能如下:
```cpp
#include "MyVector.h"
template class MyVector<int>; // instantiate in this translation unit; use this point of instantiation
```
#### 3. 名称绑定
定义模板函数时,应尽量减少对非局部信息的依赖。因为模板将用于根据未知类型和未知上下文生成函数和类,任何细微的上下文依赖都可能给使用者带来问题。在模板代码中,应特别严格地遵循尽量避免使用全局名称的原则,尽量使模板定义具有自包含性,并以模板参数的形式提供原本可能是全局上下文的信息。不过,为了实现模板的优雅表达,有时也需要使用一些非局部名称,此时优先选择命名空间而不是全局作用域。
操作符(如 `+`、`*`、`[]` 和 `sort()`)也是模板定义中使用非局部名称的一个来源。例如:
```cpp
bool tracing;
template<typename T>
T sum(std::vector<T>& v)
{
T t {};
if (tracing)
cerr << "sum(" << &v << ")\n";
for (int i = 0; i!=v.size(); i++)
t = t + v[i];
return t;
}
// ...
#include<quad.h>
void f(std::vector<Quad>& v)
{
Quad c = sum(v);
}
```
在上述代码中,`sum()` 函数依赖于一些未在其定义中显式指定的名称,如 `tracing`、`cerr` 和 `+` 操作符。
查找模板中显式或隐式使用的每个名称的声明的过程称为名称绑定。模板名称绑定的一般问题在于,模板实例化涉及三个上下文,且它们不能完全分离:
- 模板定义的上下文
- 参数类型声明的上下文
- 模板使用的上下文
为了便于处理,语言将模板定义中使用的名称分为两类:
- 依赖名称:依赖于模板参数的名称,在实例化点进行绑定。
- 非依赖名称:不依赖于模板参数的名称,在模板定义点进行绑定。
下面是名称绑定的详细分类介绍:
|名称类型|绑定规则|示例|
| ---- | ---- | ---- |
|依赖名称|在实例化点进行绑定,函数调用依赖模板参数需满足特定条件|`template<typename T> T f(T a) { return g(a); }`,`g` 是依赖名称|
|非依赖名称|在模板定义点进行绑定,必须在定义点可见|`template<typename T> T f(T a) { ++x; }`,`x` 是非依赖名称|
##### 3.1 依赖名称
简单来说,“N 依赖于模板参数 T” 可以被定义为 “N 是 T 的成员”,但这并不完全准确。函数调用依赖于模板参数需满足以下条件之一:
- 实际参数的类型根据类型推导规则依赖于模板参数 T。
- 被调用函数的参数根据类型推导规则依赖于 T。
默认情况下,依赖名称被假定为非类型名称,若要将其作为类型使用,需使用关键字 `typename`。例如:
```cpp
template<typename Container>
void fct(Container& c)
{
Container
```
0
0
复制全文
相关推荐










