类模板可以继承也可以被继承。一般模板和非目标的继承没有什么区别。然而,要从依赖型名称所引用的基类派生一个类模版的情况下,这两者之间有一个重要的区别。让我们来看个例子
非依赖型基类
在一个类模板中,一个非依赖型基类是指:无需知道模板实参就可以完全确定类型的基类。也就是说,基类名称是用非依赖型名称来表示的。
template<typename X>
class Base{
public:
int basefield;
typedef int T;
};
class D1 : public Base<Base<void>>{ // 实际上不是模板
public:
void f(){ basefield = 3;}
};
template<typename T>
class D2 : public Base<double>{ // 非依赖型基类
public:
void f(){ basefield = 7;} // 正常访问继承成员。
T strange; //T 是Base<double>::T, 而不是模板参数
};
模板中的非依赖型基类和普通非模板类中的基类的区别在于:对于模板中的非依赖型基类而言,如果在它的派生类中查找一个非受限名称,那么就会先查找这个非依赖型基类,然后才查找模板参数列表。也就是说,下面是错的:
void g(D2<int *> & d2, int *p){
d2.strange = p; // 错误,类型不匹配
}
依赖型基类
C++标准规定:
- 对于模板中的非依赖型名称,将会在看到的第一时间查找
- 非依赖型名称不会在依赖型基类中进行查找
template<typename X>
class Base{
public:
int basefield;
typedef int T;
};
template<typename T>
class DD : public Base<T>{
public:
void f() { basefield = 0}; //error
};
修改方案:
- 方案1:让basefield也成为依赖型名称(推荐)。 依赖型名称只有在实例化是才会进行查找,而且实例化时,基类的特化是已知的
template<typename X>
class Base{
public:
int basefield;
typedef int T;
};
template<typename T>
class DD : public Base<T>{
public:
void f() { this->basefield = 0; }; //依赖型名称,查找被延迟了
};
- 方案2:利用受限名称来引入依赖性。
template<typename T>
class DD : public Base<T>{
public:
void f() { Base<T>::basefield = 0; }; //(1)非依赖型名称
};
但是方案2有一个缺点:如果原来的非受限的非依赖型名称是被用于虚函数调用的话,那么这种引入依赖性的限定将会禁止虚函数调用,从而也会改变程序的含义。因此,但方案2不合适时请用方案1
template<typename X>
class B{
public:
enum E{e1 = 6, e2 = 28, e3 = 496};
virtual void zero(E e = e1);
virtual void one(E&);
};
template<typename T>
class D : public B<T>{
public:
void f(){
typename D<T>::E e; // this->E会是一个无效的调用
this->zero(); //D<T>::zero()会禁止虚函数调用
one(e); //one是一个依赖性名称,因为它的实参是依赖型的
}
};
建议:允许使用this->前缀的地方都使用this->
- 方案3: 不断重复的限定也能会使得代码不好看,这时可以在派生类中引入依赖性基类
template<typename T>
class D : public Base<T>{
public:
using Base<T>::basefield;
void f(){ basefield = 0;}
};