Class Templates 可以继承其他classes,也可以被其他classes继承。大多数情况下 class templates 和 non-templates classes 在这方面并没有什么重大差别。但是有一个微妙和重要的问题,出现在“由一个受控名称dependent name 所指涉的base class,衍生出一个class template”时,我们先从简单的non-dependent base classes说起。
1. 非受控的Non-dependent Base Classes
在class template中, 所谓non-template base classes 是指一个无需知道任何template argument就可以确定地类型。换句话说,用来指涉该base class的名称,是个非受控名称non-dependent name。例如:
template <typename X>
class Base{
public:
int basefield;
typedef int T;
};
class D1:public Base<Base<void> > { //并不真正是个template
public:
void f() {basefield = 3; } // 以通常方法继承来的成员
};
template <typename T>
class D2: public Base<double> { // non-dependent base class
public:
void f() {basefield = 7;} // 以一般方法存取继承而来的成员
T strange; //T是Base<double>::T, 不是模板参数template parameters
};
non-dependent base templates的行为和常规的non-template base classes 非常相似,但是有个令人惊异的差异是:当编译器在derived class template中查询一个未受饰名称unqualified name时,会优先查询non-dependent base templates,然后才查询template parameters.这意味着上面例子中,class template D2的strange将拥有Base<double>::T类型(本例为int)。
因此以下函数语句非法:
void g(D2<int*> & d2, int* p)
{
d2.strange = p; // 错误,类型不匹配:trange是int, p 是int*
}
这与直觉相悖(感觉int*,就是模板实参传给了T),derived template 编写者因此特别留心其non-dependent bases中的名称,甚至是简介继承,或名称是private。较好的做法是把template parameters 放在被它们模板化的物体的作用域内。
2. 受控的Dependent base Classes
上面的例子中,base class是完全确定的。它不取决于某个template parameter。也就是说C++
编译器只要是见到template的定义,就可以在base classes中查询非受控名称。C++标准规定,编译器只要见到一个非受控名称non-dependent name,就立即开始查询lookup。
考虑下面的例子:
template <typename T>
class DD: public Base<T> { //dependent base
public:
void f() {basefield = 0;} //(1) have some problem...
};
template <>
class Base<bool> { //explicit specialization
enum { basefield = 42 }; // (2) a tricky
};
void g(DD<bool>& d)
{
d.f(); //(3) oh,oh?
}
在(1)处我们发现,这里使用了一个非受控名称basefield:编译器一定会立即查询它。假设我们在template base中查询它,并将它系结为一个(在基类中找到的)int成员。然而之后,又立刻在(2)对这个泛型定义进行了特化,将basefield改成我们看到的枚举变量。于是当(3)处实例化DD:f的定义时,会发现(1)处对basefield的类型解释并不准确。
(2)处特化的DD<bool> 之中并没有可变化的basefield,因此编译器会报错。
为了解决这个问题,C++标准规定非受控名称non-dependent names不在受控的base class中查询。因此符合标准的C++编译器会在(1)处报错。欲修正上述程序代码,我们可以令basefield成为一个受控名称dependent name,而受控名称的查询过程会发生在它被实例化之后。此时basefield的特化类型就可以被编译器发现。例如,在(3)处,编译器会知道DD<bool>的base class是Base<bool>,而Base<bool>已被实例化。据此,我们可以修改程序代码如下:
//method 1
template <typename T>
class DD1: public Base<T> {
public:
void f() { this->basefield = 0;} //delay the lookup
};
另一种做法是使用一个受饰名称qualified name导入相依性(受控性,dependency)
//method2
template <typename T>
class DD2:public Base<T> {
public:
void f() { Base<T>::basefield = 0; }
};
如果觉得这么多修饰符号会弄乱程序代码,可用using声明语句将某个名称从受控的base class中带到当前作用域中,于是你就可以肆意使用它。
//method 1
template <typename T>
class DD3: public Base<T> {
public:
using Base<T>:: basefield; //(1) 现在,basefiled在当前域中为受控名称
void f() { basefield =0; } //(2)正确
};
(2)进行的查询过程就会成功,它会找到(1)处的声明语句。