C++模板特化详解

首先强调一句话:模板特例化首要目标是保证编译通过!!!

一、为什么需要模板特化

这周四,帮一个帅小伙儿排查问题,他写了一段类似于这样的逻辑:

class student{
    int age = 25;
public:
    void mycout(){
        cout<<age<<endl;
    }
};

template<typename T>
class Base
{
    T t;
public:
    Base(T t):t(t){}
    void display(){
        if(1==0){
            cout<<t<<endl;
        } 
    }
};

int main() {
    student stu;
    Base<student> base(stu);
    base.display();
}

看起来能编译通过,因为cout<<t<<endl,也就是cout<<stu<<endl这个不合理的逻辑永远不会被执行。但是,这个是编译不过的。
因为实例化后,编译器会二次编译实例化后的代码,cout<<stu这种行为,在二次编译的过程中会被检查出来有语法错误。
有个有意思的事情,去掉base.display();后,编译不会报错。根据《C++ primer》中的解释,对于类模板中的成员函数,即使在类模板实例化以后,如果这个函数不被调用,那么也不会被具体化。
言归正传,这个问题的解决方案就是模板特化!!!

二、类模板特例化

我们定义类模板的时候,一定要考虑它可能被实例化成哪些类型。假如这个类模板有可能被实例化的方式有100种,其中99种是通用的,但是有一种实例化方式,实例化后是编译不过的。可以采用模板特化的方法,单独把这种类型拎出来,告诉编译器,碰到这种特殊的类型,按照特殊的方式展开模板。
看代码:

template<typename T>
class Base
{
    T t;
public:
    Base(T t):t(t){}
    void display(){
        cout<<t<<endl;
    }
};

template<>
class Base<student>
{
    student stu;
public:
    Base(student s):stu(s){}
    void display(){
        stu.mycout();
    }
};

int main() {
    student stu;
    Base<student> base1(stu);
    base1.display();

    Base<int> base2(12);
    base2.display();
    Base<string> base3("abc");
    base3.display();
    Base<char> base4('a');
    base4.display();
}

当你将模板参数实例化为student时候,模板类就会按照特例化版本展开。对于其它类型,就会按照通用版本展开。
看到这里你可能有个疑问,特例化版本的模板,必须和通用模板类成员上保持一致吗?答案是否定的。还是那句话,编译器只是负责将模板展开,你只要保证展开后没有语法错误就可以了。

template<typename T>
class Base
{
    T t;
public:
    Base(T t):t(t){}
    void display(){
        cout<<t<<endl;
    }
};

template<>
class Base<student>
{
public:
    void display(){
        cout<<"1233211234567"<<endl;
    }

    void f(){}
};

int main() {
    Base<student> base1;
    base1.display();
}

正常输出。

三、继承关系下的模板特例化

但是,帅小伙儿想要特例化的模板,是一个继承于基类类的类模板,这就要多考虑考虑。看代码:

class student{
    int age = 25;
public:
    void mycout(){
        cout<<age<<endl;
    }
};

class Base
{
    int a;
public:
    Base(int t):a(t){}
    void display(){
        cout<<a<<endl;
    }
};

template<typename T>
class Derived : public Base
{
    T t;
public:
    Derived(T t):Base(123),t(t){}
    void display(){
        Base::display();
        cout<<t<<endl;
    }
};

template<>
class Derived<student> : public Base
{
public:
    Derived():Base(123){}
    void display(){
        Base::display();
        cout<<"te li hua"<<endl;
    }
};

int main() {
    Derived<student> d;
    d.display();
}

运行结果:

123
te li hua

正如前文所说,特例化版本没必要和通用版本成员保持一致,所以我故意给特例化版本写了一个无参构造函数。
实际上,那个帅小伙儿写的基类里面,有纯虚函数,继承的时候还必须重写。我们不禁想到一个问题:通用版本类模板是派生类,那么它的特例化版本也必须是派生类吗?
答案是否定的,还记得我开篇强调的那句话吗?看代码:

class student{
    int age = 25;
public:
    void mycout(){
        cout<<age<<endl;
    }
};

class Base
{
    int a;
public:
    Base(int t):a(t){}
    void display(){
        cout<<a<<endl;
    }
};

template<typename T>
class Derived : public Base
{
    T t;
public:
    Derived(T t):Base(123),t(t){}
    void display(){
        Base::display();
        cout<<t<<endl;
    }
};

template<>
class Derived<student>
{
    student stu;
public:
    Derived(student stu):stu(stu){}
    void display(){
        stu.mycout();
        cout<<"te li hua"<<endl;
    }
};

int main() {
    student stu;
    Derived<student> d(stu);
    d.display();

    Derived<int> d1(456);
    d1.display();
}

运行结果:

25
te li hua
123
456

为什么没问题呢?因为特例化就是想让编译器编译没有问题。当编译器看到 Derived d(stu);的时候,自然按照特化版本去展开编译。展开结果没问题就可以了,至于说继承关系变了毫无影响,编译器只管你能不能编译过,至于你继承谁,编译器才不管呢。因为理论上按照通用类模板实例化展开的各个类,彼此之间也是没有联系的。
你甚至可以玩个骚操作,特例化版本继承自其它父类:

class student{
    int age = 25;
public:
    void mycout(){
        cout<<age<<endl;
    }
};

class Base
{
    int a;
public:
    Base(int t):a(t){}
    void display(){
        cout<<a<<endl;
    }
};

template<typename T>
class Derived : public Base
{
    T t;
public:
    Derived(T t):Base(123),t(t){}
    void display(){
        Base::display();
        cout<<t<<endl;
    }
};

class Test{
public:
    void display(){
        cout<<"I am Test class"<<endl;
    }
};

template<>
class Derived<student> : public Test
{
    student stu;
public:
    Derived(student stu):stu(stu){}
    void display(){
        stu.mycout();
        cout<<"te li hua"<<endl;
        Test::display();
    }
};

int main() {
    student stu;
    Derived<student> d(stu);
    d.display();

    Derived<int> d1(456);
    d1.display();
}

运行结果:

25
te li hua
I am Test class
123
456

还是那句话,你特例化了,编译器就按照特例化方式展开。编译器才不管继承自谁呢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值