类模板的定义
我们拿数组Array来举例:
class Array{
public:
int getSize() const {
return 10;
}
private:
int elem[10];
};
类Array中有一个长度为10的C数组。其中的数组类型和长度显然是可以泛化的。下面将其变为一个模板:
#include <cstddef> // (1) std::size_t
#include <iostream>
#include <string>
template <typename T, std::size_t N> // (2)
class Array{
public:
std::size_t getSize() const {
return N;
}
private:
T elem[N];
};
int main() {
std::cout << '\n';
Array<int, 100> intArr; // (3)
std::cout << "intArr.getSize(): " << intArr.getSize() << '\n';
Array<std::string, 5> strArr; // (4)
std::cout << "strArr.getSize(): " << strArr.getSize() << '\n';
Array<Array<int, 3>, 25> intArrArr; // (5)
std::cout << "intArrArr.getSize(): " << intArrArr.getSize() << '\n';
std::cout << '\n';
}
成员函数的定义
在类里面定义成员函数很简单。就像上面那样。
当你在类外定义成员函数时,必须指定它是模板,并且必须指定类模板的完整类型声明。如下所示:
template <typename T, std::size_t N>
class Array{
public:
std::sizt_ getSize() const;
private:
T elem[N];
};
template <typename T, std::size_t N> // (1)
std::size_t Array<T, N>::getSize() const {
return N;
}
其中(1)是将成员函数定义在类外的写法。如果成员函数本身就是一个模板,那么写起来会更加不方便。
当成员函数为模板
泛型成员函数的一个典型例子是模板化的赋值操作符重载。例如,将Array<T, N>
赋值给 Array<T2, N2>
,前提是T可以赋值给T2并且它们的大小相同。
将Array<float, 5>
赋值给 Array<double, 5>
是不合法的,因为它们类型不同。
下面是一个初步的Array实现,其支持了2个长度相同的数组赋值。其中的C数组elem是故意public修饰的。
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr) {
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
std::size_t getSize() const {
return N;
}
T elem[N];
};
赋值操作符Array<T, N>& operator = (const Array<T2, N>& arr)
可以接受长度相同类型不同的数组。
友元
可以通过友元将elem变为private。
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr) {
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
template<typename, std::size_t> friend class Array; // (1)声明所有Array的实例为友元
std::size_t getSize() const {
return N;
}
private:
T elem[N];
};
将成员函数定义在类外
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr);
template<typename, std::size_t> friend class Array;
std::size_t getSize() const;
private:
T elem[N];
};
template <typename T, std::size_t N>
std::size_t Array<T, N>::getSize() const { return N; }
template<typename T, std::size_t N> // (1)
template<typename T2>
Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) {
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
在类外定义泛型成员函数比较复杂。现在,如果T和T2是不可转换的,那么赋值操作将会报错。
可以在编译时进行类型的检查,C++11中可以使用static_assert和type_traits库,在C++20中可以使用concepts。
- C++11 主要代码:
template<typename T, std::size_t N>
template<typename T2>
Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) {
static_assert(std::is_convertible<T2, T>::value, // (1) 检查是否能从T2转换为T
"Cannot convert source type into the destination type!");
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
- C++20 全部代码:
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <string>
#include <concepts>
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr) requires std::convertible_to<T2, T>; // (1)
template<typename, std::size_t> friend class Array;
std::size_t getSize() const;
private:
T elem[N];
};
template <typename T, std::size_t N>
std::size_t Array<T, N>::getSize() const { return N; }
template<typename T, std::size_t N>
template<typename T2>
Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) requires std::convertible_to<T2, T> { // (2)
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
int main() {
std::cout << '\n';
Array<float, 5> floatArr;
Array<float, 5> floatArr2;
floatArr.getSize();
floatArr2 = floatArr;
Array<double, 5> doubleArr;
doubleArr = floatArr;
Array<std::string, 5> strArr;
// doubleArr = strArr; // (3)
}
当打开注释(3),编译器将会报错。