C/C++编程:模板特例

1059 篇文章 286 订阅

为什么要引入

单一模板很难兼顾各种情况。为此,C++提供了一种特例机制,对于某个已有模板,可以为某个或者某组模板参数类型另外指定一种变体,以应对原模板无法处理的情况,或者提供更高效的实现方案,这就是模板特例

实例:vector<bool>

vector<bool>vector<T>的一个特例,专为存储bool型值设计。现在我们自己写一个vector<bool>来为存储bool值提供更高效的方案吧。功能:

  • 构造函数中需要声明my_vector初始大小
  • 可以通过成员函数push_back()向my_vector中增加元素
  • 重载下标运算符[]共读写my_vector中元素
template<typename T>
class my_vector{
    T* array;
    unsigned size;
    unsigned block_size;
public:
    my_vector(unsigned bsz) :
         array((T*)malloc(sizeof(T) * bsz)), size(0), block_size(bsz) {}
    ~my_vector() {
        if(array)
            free(array);
    }

    void push_back(T const &elem) noexcept(false){
        if(size == block_size){
            block_size *= 2;
            T* new_array = (T*) realloc(array, block_size * sizeof(T));
            if(new_array){
                array = new_array;
            }else{
                free(array);
                array = nullptr;
                throw std::runtime_error("Out of memory");
            }
        }
        array[size++] = elem;
    }

    T& operator[](unsigned i){
        return array[i];
    }

    const T& operator[](unsigned i) const {
        return array[i];
    }

    unsigned get_mem_size() const {
        return block_size * sizeof(T);
    }
};

sizeof(bool)的返回值为1,即1字节(byte)。但是bool型变量只有两个值,只需要1bit就可以表示,一个字节可以用来存储8个bool值。位置,我们设置了一个bool型的模板特例。当使用my_vector<bool>编译器会自动选用预设的特别方案。

  • 特例是相对于通例而言的。也就是说,必须先有my_vector<T>才可以有my_vector<bool>
  • 所谓模板特例,是针对符合某种条件的模板参数值集合另外声明的模板实现变体。所以,每个特例都需要有模板参数匹配式以表明其适用范围。在声明特例时,匹配式写在模板名后面并由<>括起。而原本的模板参数列表改为匹配式所用模板参数列表。说人话就是:
template<typename T> class my_vector; // 通例
template<> class my_vector<bool>; // 特例

下面我们来看具体实现:

template<>
class my_vector<bool>{
    int *array;
    unsigned size;
    unsigned block_size;
    const static unsigned seg_size;
public:
    my_vector(unsigned  bsz = 1) :
        array((int *) malloc(sizeof(int) * bsz)),
        size(0),
        block_size(bsz){}
    ~my_vector() {
        if(array){
            free(array);
        }
    }

    void push_back(bool elem) noexcept(false){
        if(size == block_size * seg_size){
            block_size *= 2;
            int* new_array = (int*) realloc(array, block_size * sizeof(int));
            if(new_array){
                array = new_array;
            }else{
                free(array);
                array = nullptr;
                throw std::runtime_error("Out of memory");
            }
        }
        set(size++, elem);
    }

    void set(unsigned i, bool elem){
        if(elem){
            array[i/seg_size] |= (0x1 << (i % seg_size));
        }else{
            array[i/seg_size] &= ~(0x1 << (i % seg_size));
        }
    }

    bool operator[] (unsigned i)const{
        return (array[i/seg_size] & (0x1 << (i % seg_size))) != 0;
    }

    unsigned get_mem_size() const {
        return block_size * sizeof(int);
    }
};

const unsigned my_vector<bool>::seg_size = sizeof(int ) * 8;

使用:

int main(){
    my_vector<char> vi(2);
    my_vector<bool> vb(2);
    for(unsigned  i = 0; i < 20; i++){
        vi.push_back('a' + i);
        vb.push_back( (i % 2) == 0);
    }

    printf("%d, %d\n", vi.get_mem_size(), vb.get_mem_size());
    for(unsigned  i = 0; i < 20; i++){
       std::cout << ' ' << vi[i];
    }
    std::cout << "\n";

    for(unsigned  i = 0; i < 20; i++){
        std::cout << ' ' << vb[i];
    }
    std::cout << "\n";
}

C++对于特例和通例没有任何约束,理论上可以把特例设计成一个与通例毫无共同点的例,不过最好不要这样做。

类模板

特例的多种写法

// 用于模板型模板参数的模板
template<typename T, int i> class S1;

// 模板通例,有三个模板参数:类型参数,非类型参数、模板型参数
template<typename T, int i, template<typename, int>class SP>
struct S;

// 特例1:可以匹配S<char, 任意整数,S1>
template<int i, template<typename, int>class SP>
struct S<char, i, SP>;

// 特例2: 可以匹配S<任意有const修饰的类型, 任意整数, S1>
template<typename T, int i, template<typename, int>class SP>
struct S<const T, i, SP>;

// 特例3: 完全特例,只能匹配S<char, 10, S1>
template<>
struct S<char, 10, S1>

//特例4:以模板实例作为类型参数值,匹配S<S1<任意类型, 10>, 10, S1>
template<typename T>
struct S<S1<T, 10>, 10, S1>

//特例5:错误!匹配项目数和通例个数不一致
template<typename T, int i, template<typename, int>class SP, template TT>
struct S<const T, i, SP, TT>;

// 特例6:错误!匹配式项目类型和通例参数类型不一致
template<typename T>
struct S<char, 10, T>

//特例7: 错误! 模板型参数SP和通例中的SP类型不一致
template<typename T, int i, template<typename>class SP>
struct S<const T, i, SP>;

特例匹配规则

同一模板中可以有多个实例。

  • 当某套模板参数能与多个特例匹配时,编译器会从中优先选择最“特殊”的一个。
  • 如果有任意两个特例之间无法确定谁更特殊时,则代码有歧义,编译报错
#include <iostream>

template<typename T0, typename T1, typename T2>
struct S{
    std::string id(){
        return "general";
    }
};

// 特例1:约束第3参数必须为char
template<typename T0, typename T1>
struct S<T0, T1, char>{
    std::string id(){
        return "specializetion #1";
    }
};


// 特例2:约束第2、3参数必须为char
template<typename T0>
struct S<T0, char, char>{
    std::string id(){
        return "specializetion #2";
    }
};

// 特例3:约束第1参数必须为int, 第2、3参数必须相同
template<typename T0>
struct S<int, T0, T0>{
    std::string id(){
        return "specializetion #3";
    }
};

int main(){
    printf("%s\n", S<float, float, float>().id().c_str());  //实例1
    printf("%s\n", S<int, int, int>().id().c_str());  //实例2
    printf("%s\n", S<int, int, char>().id().c_str());  //实例3
    printf("%s\n", S<char, char, char>().id().c_str());  //实例4
   // printf("%s\n", S<int, char, char>().id().c_str());  //实例5, 有歧义
}

在这里插入图片描述

模板函数中的特例与重载

C++标准中只允许为函数模板声明完成特例,禁止为其声明部分特例

#include <iostream>

template<typename T>
void print(T v){
    std::cout << v << std::endl;
}

//模板特例
template<>
void print<char>(char v){
    std::cout << "\'"<< v << "\'"<<  std::endl;
}

//模板特例,模板参数依赖推导
template<>
void print(const char *v){
    std::cout << "\""<< v << "\""<<  std::endl;
}

// 函数重载
inline 
void print(std::string const &v){
    std::cout << "\""<< v << "\""<<  std::endl;
}

inline 
void print(bool v){
    std::cout << std::boolalpha << v << "\n";
}

// 函数模板重载
template<typename T>
void print(T *v){
    std::cout << "*";
    print(*v);
}

分辨重载

编译器分辨重载函数的基本过程:

  • 首先,对于某个函数调用,编译器已知所调用函数名、参数个数和类型等。
  • 当发现有同名重载函数模板时,会根据参数类型一次尝试推导出模板参数类型
  • 如果推导成功,则生成对应函数模板实例作为候选
  • 如果发现有同名函数,也将该函数列入候选
  • 但是如果调用明确要求一个模板实例,即函数名后紧跟一对<>,则不将普通重载函数列入候选
  • 至此,候选集合中无论函数模板实例还是普通函数,都可以视为有效的函数定义(而非模板),从而可以按照C++中对普通重载函数调用分辨重载的规则确定最佳选择。

我们只需要知道这三个分辨重载的准则即可:

  • 两候选函数中如果有一方其形参列表各类型与调用实参各类型更匹配,则淘汰另一方。
    • 参数匹配类型的一般原则是:匹配度从高到低
      • 等价类型: 比如char[]和char*;由实参类型char *转换到形参类型const char*
      • 标准类型转换:比如实参类型int转换成long
      • 自定义类型转换:比如将实参类型int转换成用户定义类型A,而且A有构造函数A(int)
  • 两函数如果其形参列表同等匹配实参列表类型时,如果一方为函数模板实例二另一方为非模板函数,则取非模板函数而淘汰函数模板实例
  • 两函数如果其形参列表同等匹配实参列表类型时,如果两者均为函数模板实例,而取更为特殊的一方而淘汰另一方。

看个例子:

#include <iostream>

template<typename T>
void func(T v){
    std::cout << "#1:" << v << "\n";
}

template<>
void func(float v){
    std::cout << "#2:" << v << "\n";
}



void func(float v){
    std::cout << "#3:" << v << "\n";
}


int main(){
   func(1);
   func(1.);
   func(1.f);
   func<>(1.f);
}

在这里插入图片描述
分析:

  • func(1):因为函数模板#1可以根据实参类型推导出模板参数T=int,所生成的函数实例中形参与实参类型完全一致,所以#1
  • func(1.):推导出模板参数为T=double,因此同上
  • func(1.f):1.f指定为double,因此选择非模板函数#3
  • func<>(1.f)显式的指出了一个模板实例调用,因此不考虑#3,又因为#2比#1更特殊,所以选择#2

注意:

  • 模板实例(#2)和重载普通函数(#3)都接受一个浮点型参数,二者的参数定义完全一致。但这并不违法C++的唯一性原则。模板不是普通函数,虽然给模板特例没有模板参数,但仍然是一个模板。编译器只有在明确需要生成模板实例时才会依据模板内容生成具体函数,所以该重载模板与重载函数不冲突

编译期的条件判断逻辑

招聘题目:不使用模板以及条件判断语句,打印1-100个数组

#include <iostream>

template<int N>
void print(){
    std::cout << N << "\t";
    print<N-1>();
}

// 特例:终止递归
template<>
void print<1>(){
    std::cout << 1 << "\t";
}

int main(){
    print<100>();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值