返回

C++ 中的模板(二):类模板

对 C++ 中类模板相关概念进行整理。

简介

C++ 中的模板(一):函数模板 对函数模板做了简单的整理。这篇博客主要对类模板的相关概念也进行整理,相较于函数模板,类模板在 STL 中使用更为广泛。

类模板实例化

和函数模板类似,类模板本身不是任何实体,只是用以产生类的模板,因此本身不会产生任何汇编代码,只有通过实例化才能产生实际的代码。因此为了实例化我们需要提供对应的实例类的声明和定义。

显式实例化

实例化有两种方式:显式实例化和隐式实例化。显式实例化为在编译单元中显式地提供模板类的定义式或声明式:

template <typename T>
class Base {
public:
    Base(T t) : t(t) {}

private:
    T t;
};

template class Base<int>; // 显式提供定义式

int main() { return 0; }

编译后汇编代码为:

Base<int>::Base(int) [base object constructor]:
        mov     DWORD PTR [rdi], esi
        ret
main:
        xor     eax, eax
        ret

当我们不需要编译器在本编译单元中提供实现时,我们可以只进行声明而不定义,如下所示:

template <typename T>
class Base {
public:
    Base(T t) : t(t) {}

private:
    T t;
};

extern template class Base<int>; // 模板类声明

int main() { return 0; }

此时编译后不会产生模板类的汇编代码:

main:
        xor     eax, eax
        ret

隐式实例化

对于类模板而言,隐式实例化稍微复杂一点,当代码涉及到类模板,且满足以下条件时,隐式实例化会被触发,编译器会对涉及到的实例对模板进行实例化:

  • 该代码需要完整定义(completely defined)的类型或类型的完整性影响代码时
  • 这个特定类型没有被显式实例化时(包括显式定义或者显式声明)

下面来看一些例子:

template <typename T>
class Base {
public:
    Base(T t) : t(t) {}

    void func1() { t = t + T(1.0); }

    void func2();

private:
    T t;
};

int main() {
    Base<int> b_int(1);

    Base<float>* pb_float;

    Base<double>* pb_double = new Base<double>(10.0);
    pb_double->func1();
    return 0;
}

下面分别来看一下:

  • Base<int> b_int(1); 这里,b_int 被初始化,因此需要 Base<int>::Base() 的定义,其本身没有被显式实例化,因此触发隐式实例化,Base<int> 的构造函数被实例化
  • Base<float>* pb_float; 这里声明了一个 Base<float> 的类型的指针,但是由于没有进行初始化,即不需要 Base<float> 的构造函数,因此没有函数被初始化
  • Base<double>* pb_double = new Base<double>(10.0);: 和 b_int 类似,Base<double> 的构造函数被初始化
  • pb_double->func1();Base<double>::func1() 被调用因此被隐式初始化,Base<double>::func2() 没被调用,因此即使没被定义也不受影响

相关的汇编代码如下所示:

不完整的类(只有声明没有定义)在实例化时会报编译错误。

类模板特例化

和函数模板一样,类模板也可以进行特例化,如下所示:

#include <iostream>

template <typename T>
class Base {
public:
    Base() { std::cout << "General base.\n"; }
};

template <>
class Base<int> {
public:
    Base() { std::cout << "Specialized base.\n"; }
};

int main() {
    Base<int> b_int;
    Base<double> b_double;
    return 0;
}

运行结果为:

$ /home/xt/code_collections/cpp/build/templates/class_templates/class_template_example4
Specialized base.
General base.

在某些情况下,我们可能需要对嵌套模板类进行特例化,这个时候要求,如果我们需要对嵌套部分中某个类/函数进行特例化,那么它的外围类必须也要特例化,如下所示:

#include <iostream>

template <typename T1>
class Outter {
public:
    Outter() { std::cout << "General Outter.\n"; }

    template <typename T2>
    class Inner {
    public:
        Inner() { std::cout << "General Inner.\n"; }
    };
};

template <>  // Outter 特例化
template <typename T> // Inner 保持原样
class Outter<int>::Inner {
public:
    Inner() { std::cout << "Specialized Inner.\n"; }
};

int main() {
    Outter<int>::Inner<int> a;
    Outter<double>::Inner<int> b;
    return 0;
}

运行结果为:

$ /home/xt/code_collections/cpp/build/templates/class_templates/class_template_example4
Specialized Inner.
General Inner.

相比于函数模板,类模板还可以进行偏特化,即有多个模板参数时,可以只特例化其中部分参数,如下所示:

#include <boost/type_index.hpp>
#include <iostream>

template <typename T1, typename T2>
class Base {
public:
    Base() {
        std::cout << "General: T1 = " << boost::typeindex::type_id<T1>().pretty_name()
                  << ", T2 = " << boost::typeindex::type_id<T2>().pretty_name() << std::endl;
    }
};

template <typename T>
class Base<int, T> {
public:
    Base() { std::cout << "Specialzied: T1 = int, T2 = " << boost::typeindex::type_id<T>().pretty_name() << std::endl; }
};

int main() {
    Base<int, double> a;
    Base<double, double> b;
    return 0;
}

运行结果如下所示:

$ /home/xt/code_collections/cpp/build/templates/class_templates/class_template_example6
Specialzied: T1 = int, T2 = double
General: T1 = double, T2 = double

参考

Built with Hugo
Theme Stack designed by Jimmy