类模板继承中有一些细节需要注意。
类模板继承的成员函数是不可用的
#include <iostream>
class Base{
public:
void func(){ // (1)
std::cout << "func\n";
}
};
class Derived: public Base{
public:
void callBase(){
func(); // (2)
}
};
int main(){
std::cout << '\n';
Derived derived;
derived.callBase();
std::cout << '\n';
}
有两个类Derived和Base。Derived公有继承于Base,所以在callBase中可以调用func方法。
现在将Base变成一个模板。
#include <iostream>
template <typename T>
class Base{
public:
void func(){ // (1)
std::cout << "func\n";
}
};
template <typename T>
class Derived: public Base<T>{
public:
void callBase(){
func(); // (2)
}
};
int main(){
std::cout << '\n';
Derived<int> derived;
derived.callBase();
std::cout << '\n';
}
那么编译将报错:
Compilation Failed
/usercode/file.cpp: In member function 'void Derived<T>::callBase()':
/usercode/file.cpp:16:9: error: there are no arguments to 'func' that depend on a template parameter, so a declaration of 'func' must be available [-fpermissive]
16 | func(); // (2)
| ^~~~
/usercode/file.cpp:16:9: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
func是一个不依赖于模板参数T的名称。不依赖于模板参数的名称会在模板定义时查找。所以编译器不会在Base<T>
中查找,故找不到func方法。依赖模板参数的名称在模板实例化时查找。
上面这个查找过程叫做Two Phase Lookup。第一个阶段负责查找非依赖的名称,第二阶段查找依赖模板参数的名称。
下面有3种方案来调用func方法:
#include <iostream>
template <typename T>
class Base{
public:
void func1() const {
std::cout << "func1()\n";
}
void func2() const {
std::cout << "func2()\n";
}
void func3() const {
std::cout << "func3()\n";
}
};
template <typename T>
class Derived: public Base<T>{
public:
using Base<T>::func2; // (2) 引入func2到当前的作用域
void callAllBaseFunctions(){
this->func1(); // (1) 隐式依赖,名称查找会考虑所有的基类
func2(); // (2)
Base<T>::func3(); // (3)
}
};
int main(){
std::cout << '\n';
Derived<int> derived;
derived.callAllBaseFunctions();
std::cout << '\n';
}
建议使用第一种方法,这样在基类名称改变时仍然有效。
成员函数的实例化是lazy的
lazy意味著类模板的成员函数仅仅在被使用时才会实例化。
#include <iostream>
template<class T>
struct Lazy{
void func() { std::cout << "func\n"; }
void func2(); // not defined (1)
};
int main(){
std::cout << '\n';
Lazy<int> lazy;
lazy.func();
std::cout << '\n';
}
虽然func2函数只有声明,没有定义,编译器仍然可以正常运行。
再举一个例子:
#include <cstddef>
class Array1 {
public:
int getSize() const {
return 10;
}
private:
int elem[10];
};
template <typename T, std::size_t N>
class Array2 {
public:
std::size_t getSize() const {
return N;
}
private:
T elem[N];
};
int main() {
Array1 arr;
Array2<int, 5> myArr1;
Array2<double, 5> myArr2; // (1)
myArr2.getSize(); // (2)
}
getSize方法只为myArr2实例化,因为其调用了这个方法。
C++ Insights显示了生成的代码:
#include <cstddef>
class Array1
{
public:
inline int getSize() const
{
return 10;
}
private:
int elem[10];
public:
// inline constexpr Array1() noexcept = default;
};
template <typename T, std::size_t N>
class Array2 {
public:
std::size_t getSize() const {
return N;
}
private:
T elem[N];
};
/* First instantiated from: insights.cpp:27 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array2<int, 5>
{
public:
inline std::size_t getSize() const;
private:
int elem[5];
public:
// inline constexpr Array2() noexcept = default;
};
#endif
/* First instantiated from: insights.cpp:28 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Array2<double, 5>
{
public:
inline std::size_t getSize() const
{
return 5UL;
}
private:
double elem[5];
public:
// inline constexpr Array2() noexcept = default;
};
#endif
int main()
{
Array1 arr = Array1();
Array2<int, 5> myArr1 = Array2<int, 5>();
Array2<double, 5> myArr2 = Array2<double, 5>();
myArr2.getSize();
return 0;
}
由于有了lazy特性,我们可以使用不支持所有成员函数的模板参数来实例化类模板,只要我们不调用这些不支持的成员函数就没问题。例如:
#include <iostream>
#include <vector>
template <typename T> // (1)
class Matrix {
public:
explicit Matrix(std::initializer_list<T> inList): data(inList) {}
void printAll() const { // (2)
for (const auto& d: data) std::cout << d << " ";
}
private:
std::vector<T> data;
};
int main() {
std::cout << '\n';
const Matrix<int> myMatrix1({1, 2, 3, 4, 5});
myMatrix1.printAll(); // (3)
std::cout << "\n\n";
const Matrix<int> myMatrix2({10, 11, 12, 13});
myMatrix2.printAll(); // (4)
std::cout << "\n\n";
const Matrix<Matrix<int>> myMatrix3({myMatrix1, myMatrix2});
// myMatrix3.printAll(); ERROR (5)
}
可以看到,Matrix
有一个成员函数printAll
来打印所有元素。(3)和(4)是正确的。由于Matrix
没有重载输出操作符<<
,所以我们可以创建myMatrix3
,但是不能打印。