【Fun with C++】Template advanced

Theme of this issue: In-depth analysis of templates

Blog homepage: Xiaofeng

Share what you have learned and the problems you have encountered

The ability of the editor is limited, and if there are mistakes, I hope everyone will give me a lot of help.

1. Non-type template parameters

Template parameters classify type parameters to non-type parameters .
The type parameter is : appearing in the template parameter list, followed by the parameter type name such as class or typename. Non-type parameter : It is to use a constant as a parameter of the class (function) template, and the parameter can be used as a constant in the class (function) template.
Code example:

namespace zxf
{
    //这里的T是类型模板参数,N就是非类型模板参数
    template<class T, size_t N = 10>//也可也给缺省值的,但是和函数参数一样必须从左向右给出。
    class array
    {
        //实际当中很常用的
    public:
        T& operator[](size_t index) { return _array[index]; }
        const T& operator[](size_t index)const { return _array[index]; }

        size_t size()const { return _size; }
        bool empty()const { return 0 == _size; }

    private:
        T _array[N];//这里的N在模板里面 相当于常量使用。可以定义数组长度。
        //以前都是把N定义为一个宏,但是又把N写死了,无法在修改。
        size_t _size;
    };    

    //也可可以这样定义函数模板
    template<class T, size_t N = 100>
    void fun(T a = -100)
    {
        cout << a << ' ' << N << endl;
    }
}
We can see that there is also an array (fixed-length array) in the C++ function library. Its underlying layer uses only non-type template parameters.
What is the difference between him and vector?
The difference is that it can be inserted discontinuously, while the vector must be inserted continuously, (data can only be inserted one by one from the end)
a array can use its iterator or [] to directly insert data at any position.
It is compared to the static array of C language.
int arr[10];
int <int,10> arr2;
And its data is stored in the stack area like arr[10], and the data of vector is generally stored in the heap area.
The real advantage:
C language is not good enough to check out-of-bounds. It does not check for out-of-bounds reading,
and it may not be able to check for out-of-bounds writing. Array can be checked directly in the template, whether it is read or written. Directly Assertion error.
Note: 1. Floating point numbers, class objects, and strings are not allowed as non-type template parameters. 2. Non-type template parameters must be able to confirm the result at compile time.


int main()
{
    //类的模板
    zxf::array<int, 100> arr;
    arr[0] = 1;
    arr[1] = 2;
    arr[33] = 3;
    arr[4] = 4;
    arr[2] = 5;
    //其他的不初始化就是随机值
    for (int i = 0; i < 100; i++)
    {
        cout << arr[i];
    }
    cout << endl;


    //函数的模板
    zxf::fun<int, 1000>(1);//必须显示实例化了
    //非类型模板参数不能是浮点数,类对象,字符串类型
    //只能是整形家族的类型。


    这样也是不允许的
    //size_t n = 1000;
    //zxf::fun<int, n>(1);
    非类型模板参数不能是变量
    return 0;
}

2. Specialization of class templates

2.1. Concept

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板.
案例引入:
namespace zxf
{
    // 函数模板 -- 参数匹配
    template<class T>
    bool Less(T left, T right)
    {
        return left < right;
    }
    int main()
    {
        cout << Less(1, 2) << endl; // 可以比较,结果正确
        Date d1(2022, 7, 7);
        Date d2(2022, 7, 8);
        cout << Less(d1, d2) << endl; // 可以比较,结果正确
        Date* p1 = &d1;
        Date* p2 = &d2;
        cout << Less(p1, p2) << endl; // 可以比较,结果错误
        return 0;
    }
}

可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指 向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指 针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特 化中分为函数模板特化与类模板特化。

2.2 函数模板特化

函数模板的特化步骤

1. 必须要先有一个基础的函数模板 2. 关键字template后面接一对空的尖括号<> 3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,不同编译器可能会报一些奇怪的错误。
namespace zxf
{
    // 函数模板 -- 参数匹配
    template<class T>
    bool Less(T left, T right)
    {
        return left < right;
    }

    //这个是原来模板的特化
    //也就是对Date* 类型参数进行了特化,特殊化处理。
    template<>
    bool Less<Date*>(Date* left, Date* right)
    {
        return *left < *right;
    }
    //但是函数支持重载
    //直接写也可以,//但是类模板就不行了。
    bool Less(Date* left, Date* right)
    {
        return *left < *right;
    }

    int main()
    {
        cout << Less(1, 2) << endl; // 可以比较,结果正确
        Date d1(2022, 7, 7);
        Date d2(2022, 7, 8);
        cout << Less(d1, d2) << endl; // 可以比较,结果正确
        Date* p1 = &d1;
        Date* p2 = &d2;
        cout << Less(p1, p2) << endl; // 可以比较,结果错误
        //此时我们需要特化的就是Date* 类型的。
        return 0;
    }
}
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给 出。
bool Less(Date* left, Date* right)
{
 return *left < *right;
}
所以函数模板一般不建议特化处理,直接重载会更好。
但是类模板就必须要用特化处理了。

2.3 类模板特化

2.3.1 全特化

全特化即是将模板参数列表中所有的参数都确定化。
template<class T1, class T2>
class Data
{
public:
     Data() {cout<<"Data<T1, T2>" <<endl;}
private:
     T1 _d1;
     T2 _d2;
};
//全特化
template<>
class Data<int, char>
{
public:
     Data() {cout<<"Data<int, char>" <<endl;}
private:
     int _d1;
     char _d2;
};

2.3.2 偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
偏特化分为两种:1,对部分参数特化;2,对参数加以限制.
template<class T1, class T2>
class date
{
public:
    date() { cout << "date<T1,T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};

//全特化
template<>
class date<double  ,double >
{
public:
    date() { cout << "date<double,double>" << endl; }
private:
    double _d1;
    double _d2;
};

//半特化/偏特化

//对部分参数进行特化
template<class T1>
class date<T1 , char>
{
public:
    date() { cout << "date<T1,char>" << endl; }
private:
    T1 _d1;
    char _d2;
};

//对部分参数进行限制
template<class T1, class T2>
class date<T1*, T2*>
{
public:
    date() { cout << "date<T1*,T2*>" << endl; }
private:
    T1* _d1;
    T2* _d2;
};
//或者
template<class T1, class T2>
class date<T1&, T2&>
{
public:
    date(const T1& d1, const T2& d2)
        :_d1(d1)
        ,_d2(d2)
    { cout << "date<T1&,T2&>" << endl; }
private:
    const T1& _d1;
    const T2& _d2;
    //注意引用在定义的时候必须初始化
    //所以我们在初始化列表中初始化了值
    //T1& _d1;
    //T2& _d2;
};

int main()
{
    //不特化
    date<int, int> d1;

    //全特化
    date<double, double> d2;

    //半特化1:部分特化
    date<int, char> d3;
    date<char, char> d4;
    date<double, char> d5;

    //半特化2:对参数进一步限制
    date<int*, int*> d6;
    date<char*, int*> d7;
    date<char&, int&> d8('a',20);
    date<int&, double&> d9(1,2.2);

    return 0;
}
特化的匹配:
一般找最合适的,
有现成的直接用现成的,没有现成的用半成品,没有半成品自己生成

3.模板的分离编译

为什么模板我们提倡不能分离编译.
解决方法 : 1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。 2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

4. 模板总结

4.1【优点】

1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生 2. 增强了代码的灵活性

4.2【缺陷】

1. 模板会导致代码膨胀问题,也会导致编译时间变长 2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

Guess you like

Origin blog.csdn.net/zxf123567/article/details/129469150