banner
Matrix

Matrix

Abyss
email
github

Petty C++ 模板

模板是 C++ 泛型的一种强大特性,可以应用于类、函数、变量,模板的存在使得 coder 可以编写类型无关的代码,而在编译时生成一般的类、函数和变量。

模板基本形式#

模板定义了一种蓝图,以生成任何可以操作任何类型的数据的类和函数:

模板的基本形式#

以常见的函数模板为例,一个模板的定义如下:

template <typename T>

T func(T param){
  // do something...
  return param;
}

上述代码是一个具有单个类型参数 T 的函数模板,其参数、返回值都是 T 类型。

  • 关键词template指明定义模板
  • 关键词typename是参数类型的占位符,T作为模板参数,编译时,编译器根据函数的调用将T的实例的类型替换为具体的参数类型,这个过程即为 “模板实例化”。

在调用时,以正常函数的方式调用:

int i = func(42);

类模板#

template <typename T>
class Array {
 private:
   T* data;
   size_t size;
 public:
   explicit Array(size_t size): size(size), data(new T[size]) {}
   ~Array() {delete[] data;}
  
   T& operator[](size_t index) {return data[index];}
}

函数模板#

template <typename T>
T max(T a, T b) {
  return (a > b)? a:b;
}

变量模板#

变量模板是 C++14 引入的新特性,可以生成任何类型的静态变量:

template <typename T>
constexpr T pi = T(3.1415)

模板的类型参数#

模板对参数数量不限制,这与普通函数一致:

template <typename T, typename U, typename V>

可以定义任意数量的类型参数的模板:

template<typename... Args> class vtclass;

template<typename... Args> 
void func(Arguments... args)

在 C++17 后,可以使用自动推导的类型,当auto用在模板参数位置时,它允许模板接受任何类型的非类型模板参数(non-type template parameter),这包括但不限于整数、字符和布尔值。这样的模板被称为自动类型推导的非类型模板参数(auto-typed non-type template parameters):

template <auto x> constexpr auto constant = x;

模板参数<auto x>表示constant可以接受任何类型的常量表达式作为参数。constant的值将会是传给它的参数x的值。

默认模板变量#

默认模板变量的声明和普通函数一致:

template <typename T = int>
void func(T factor){
  // do something
}

func<>(42);

模板的特化#

模板提供了很强大的泛型能力,但我们在定义一个模板时,不会为每一种 case 都编写重复的代码,很多情况下,我们都希望的是仅当参数类型在某些情况下有着不同的执行路径,这时候需要用到模板特化。

template <typename T, typename U>

class MyClass{}; // 定义模板

template <typename T>
class MyClass<T, int>{} // 模板特化

MyClass<int, string> myClass1; // 原始的模板

MyClass<int, int> myClass2; // 模板特化

类模板#

类模板的成员函数#

可以在类模板中定义函数,也可以在类的外部进行定义,当模板成员函数在类的外部定义时,需要前置类模板的模板声明部分来指定。

template<class T, int i>
class MyStack {
    // 类成员和方法声明
};

template<class T, int i>
void MyStack<T, i>::push(const T item) {
    // do something
}

template<class T, int i>
T& MyStack<T, i>::pop() {
    // do something
}

更常见的做法是在类模板中直接定义模板成员函数,这种写法有助于编译器进行内联处理,提高效率,同样的也能简化代码。

对于一般的非模板的函数或者类,通常的做法是在头文件中声明,在源文件中实现,但在模板编程时,为了保证模板在编译时能够访问到完整的模板定义,把模板的定义和声明都直接放在头文件中反而是最佳的实践,否则,除非为每一种类型使用的模板手动实例化,否则在进行链接时可能因为找不到模板实例的定义出现链接错误。

对于复杂大型的模板库,经常会把模板的实现代码放在.ipp 或者 .tpp文件中,然后在头文件底部包含:

// MyTemplate.h
template<typename T>
class MyTemplate {
  public:
    void func(T value);
};

#include "MyTemplate.tpp"
// MyTemplate.tpp, 实际包含模板的实现
template<typename T>
void MyTemplate<T>::doSomething(T value) {
    // 方法实现
}

嵌套类模板#

模板可以进行嵌套:

#include <iostream>

template<class T>
class A{
public:
    template <class U> 
    class B{
    private:
        U u; // 使用U类型的值而不是指针,简化示例
    public:
        B() {} // 构造函数实现
        U& Value() { return u; } // 返回U类型的引用
        void print() { std::cout << u << std::endl; } // 打印U类型的值
        ~B() {} // 析构函数实现
    };

    B<int> b;

public:
    A(T t) { b.Value() = t; }
    void print() { b.print(); } // 调用B<int>的print
};

// 在外部定义嵌套类模板成员函数时的语法示例
template<class T>
template<class U>
A<T>::B<U>::B() {
    // 构造函数具体实现
}

template<class T>
template<class U>
U& A<T>::B<U>::Value() {
    // 这个函数可以返回U类型的引用,此处只是一个示例
    return u;
}

int main() {
    A<int> a(42); // 创建A类的实例
    a.print(); // 打印42
}

函数模板#

函数模板实例化#

可以使用显式指定或推导的方式对函数进行实例化:

template<class T> void f(T) { }

template void f<int> (int);

template void f(char);

可以使用extern关键字来避免重复的代码生成(实例化)达到对代码进行精细控制的目的。


template<typename T, std::size_t N>
class MyClass {};

exterm template MyClass<int, 4>::MyClass(void);
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。