C++ Template 基础篇(二):类模板

Template 基础篇-类模板

Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第二部分。

类模板定义

类模板也是公共逻辑的抽象,通常用来作为容器(例如:vector)或者行为(例如:clonable)的封装。

类模板

下面定义了一个Printer类模板,负责打印以及转化为string。

template<typename T>
class Printer {
public:
    explicit Printer(const T& param):t(param){}
    string&& to_string();
    //定义在内部
    void print() {
        cout << t << endl;
    }
private:
    T t;
};

//定义在外部
template<typename T>
string&& Printer<T>::to_string() {
    strstream ss;
    ss << t;
    return std::move(string(ss.str()));
}

Printer p(1); //error
Printer<int> p(1); //ok

与函数模板不同,类模板不能推断实例化。所以你只能显示指定类型参数使用Printer<int> p(1),而不能让编译器自行推断Printer p(1)

类模板的成员函数既可以定义在内部,也可以定义在外部。定义在内部的被隐式声明为inline,定义在外部的类名之前必须加上template的相关声明。

类模板中的成员函数模板

我们还可以把类模板和函数模板结合起来,定义一个含有成员函数模板的类模板。

template<typename T>
class Printer {
public:
    explicit Printer(const T& param):t(param){}
    //成员函数模板
    template<typename U>
    void add_and_print(const U& u);
private:
    T t;
};

//注意这里要有两层template的说明
template<typename T>
template<typename U>
void Printer<T>::add_and_print(const U& u) {
    cout << t + u << endl;
}

Printer<int> p(42);
p.add_and_print(1.1); //自动推断U为double,打印出43.1

类模板Tips

类模板中的static成员

类模板中可以声明static成员,在类外定义的时候要增加template相关的关键词。另外,需要注意的是:每个不同的模板实例都会有一个独有的static成员对象。

template<typename T>
class Printer {
public:
    explicit Printer(const T& param):t(param){}
    static int s_value;
private:
    T t;
};

template<typename T> //注意这里的定义方式
int Printer<T>::s_value = 1;

Printer<int> pi(1);
Printer<int> pi2(1);
Printer<double> pd(1.0);
pi.s_value += 1; //pi和pi2中的改变了,pd的没改变

其实这个结论是显然的,static成员属于实例化后的类,不同的实例化当然有不同static成员。就像上面的例子一样,pi.s_value += 1只影响到了Printer<int>,而不会影响到Printer<double>

函数模板中的static局部变量也有类似的工作方式。

类模板成员函数实例化

为了节省资源,类模板实例化时并不是每个成员函数都实例化了,而是使用到了哪个成员函数,那个成员函数才实例化。

template<typename T>
class Printer {
public:
    explicit Printer(const T& param):t(param){}
    void print() {
        cout << t << endl;
    }
 private:
    T t;
 };

class empty{};

empty e;
Printer<empty> p(e); //ok

虽然成员函数print无法通过编译,但是因为没有使用到,也就没有实例化print,所以没有触发编译错误。

类模板别名

为了简化代码,我们可以使用typedef为类模板的某个实例定义一个别名,也可以使用using语句固定一个或多个类型参数(这有点偏特化的意思了)。

typedef std::pair<int, int> PairOfInt; //ok,为std::pair<int, int>定义了一个别名
template <typename T> using WithNum = std::pair<T, int>; //ok,相当于定义了一个std::pair的偏特化

PairOfInt poi; //实际类型,std::pair<int, int>
WithNum<std::string> strs; //实际类型,std::pair<string, int>
WithNum<int> ints; //实际类型,std::pair<int, int>

特化与偏特化

类模板的特化与偏特化

就像函数模板重载那样,你可以通过特化(偏特化)类模板来为特定的类型指定你想要的行为。类模板的特化(偏特化)只需要模板名称相同并且特化列表<>中的参数个数与原始模板对应上即可,模板参数列表不必与原始模板相同模板名称相同。一个类模板可以有多个特化,与函数模板相同,编译器会自动实例化那个最特殊的版本。

template<typename T> //基本模板
class S {
public:
   void info() {   
       printf("In base template\n"); 
   }
};

template<> //特化
class S<int> { 
public:
   void info() {
       printf("In int specialization\n");
   }
};

template<typename T> //偏特化
class S<T*> {
public:
   void info() {
       printf("In pointer specialization\n");
    }
};

template<typename T, typename U> //另外一个偏特化
class S<T(U)> {
public:
    void info() {
        printf("In function specialization\n");
    }
};

int func(int i) {
    return 2 * i;
}

S<float> s1;
s1.info();     //调用base模板                
S<int> s2;
s2.info();     //调用int特化版本
S<float*> s3;
s3.info();     //调用T*特化版本 
S<decltype(func)> s4;
s4.info();     //调用函数特化版本

提供了所有类型实参的特化是完全特化,只提供了部分类型实参或者T的类型受限(例如:T)的特化被认为是不完整的,所以也被称为偏特化完全特化的结果是一个实际的class,而偏特化的结果是另外一个同名的模板。*

类模板成员特化

除了可以特化类模板之外,还可以对类模板中的成员函数和普通静态成员变量进行特化。

template<typename T>  
class S {
public:
    void info() {
        printf("In base template\n");
    }
    static int code;
};

template<typename T>
int S<T>::code = 10;

template<>
int S<int>::code = 100;    //普通静态成员变量的int特化

template<>
void S<int>::info() {    //成员函数的int特化
     printf("In int specialization\n");
} 

S<float> s1;
s1.info();    //普通版本
printf("Code is: %d\n", s1.code);    //code = 10

S<int> s2; 
s2.info();   //int特化版本
printf("Code is: %d\n", s2.code);   //code = 100
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,505评论 1 51
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • C++ 模板简介 一、模板 使用模板的目的就是能够让程序员编写与类型无关的代码。 模板是一种对类型进行参数化的工具...
    MinoyJet阅读 2,343评论 0 12
  • 山行日当空,行志幻 来时未提壶,口之竭 路人行色匆,汗如滴 遥望山中亭,心窃之 步步猛如虎,欲至此 待到亭中栖,乏...
    凝侍阅读 337评论 0 1
  • 这次旅行,有两个触发点,一是两年没有出去旅行了,而贵州又是我从未去过的仅有的几个省之一;二是前不久看了一本叫做《龙...
    点滴记录阅读 1,873评论 7 17