模板的特性

1.模板参数的缺省值
1)模板参数可以带有缺省值,在未提供相应类型实参的情况下取缺省值。如果某个模板参数带有缺省值,那么它后面的所有模板参数必须都带有缺省值。
2)与函数调用参数的缺省值不同,模板参数的缺省值可以来自其前面的参数。
3)C++98不允许为函数模板的模板参数指定缺省值,但是C++2011可以。如果函数模板的模板参数的缺省值与隐式推断的类型不一致,以隐式推断的类型为准,忽略其缺省值。
GCC < 4.8 : -std=c++0x
GCC >= 4.8 : -std=c++11

#include <iostream>
#include <typeinfo>
using namespace std;
template<typename A = int,
    typename B = double,
    typename C = string>
class X {
public:
    static void foo (void) {
        cout << typeid (A).name () << ' '
            << typeid (B).name () << ' '
            << typeid (C).name () <<endl;
    }
};
/*
int mul (int x, int y = x) {
    return x * y;
}
*/
template<typename A /*= B*/,
    typename B = A>
class Y {
public:
    static void foo (void) {
        cout << typeid (A).name () << ' '
            << typeid (B).name () <<endl;
    }
};
template<typename A = int,
    typename B = double,
    typename C = string>
void foo (void) {
    cout << typeid (A).name () << ' '
        << typeid (B).name () << ' '
        << typeid (C).name () <<endl;
}
template<typename T = int>
void bar (T arg) {
    cout << typeid (arg).name () << endl;
}
int main (void) {
    X<char, short, int>::foo (); // c s i
    X<char, short>::foo (); // c s Ss
    X<char>::foo (); // c d Ss
    X<>::foo (); // i d Ss
//    X::foo ();
    /*
    cout << mul (3, 4) << endl;
    cout << mul (3) << endl;
    */
    Y<int>::foo (); // i i
    foo<char, short, int> (); // c s i
    foo<char, short> (); // c s Ss
    foo<char> (); // c d Ss
    foo<> (); // i d Ss
    foo (); // i d Ss
    bar (3.14);
    return 0;
}

 


2.非类型参数(数值参数)
1)可以为类模板或函数模板指定数值型模板参数,即非类型参数,但是可传递给非类型形参的实参必须是常量、常量表达式,或者是带有常属性(const,C限定)的变量,但是不能同时拥有挥发性(volatile,V限定)。

#include <iostream>
using namespace std;
template<typename T = int, size_t S = 3>
class Array {
public:
T& operator[] (size_t i) {
return m_a[i];
}
T const& operator[] (size_t i) const {
return const_cast<Array&> (*this)[i];
}
size_t size (void) const {
return sizeof (m_a) / sizeof (m_a[0]);
}
private:
T m_a[S];
};
template<typename T>
ostream& operator<< (ostream& os,Array<T> const& arr) {
size_t size = arr.size ();
for (size_t i = 0; i < size; ++i)
os << '(' << arr[i]/*++*/ << ')';
return os;
}
int main (void) {
Array<int> a;
a[0] = 10;
// a.operator[](0) = 10;
a[1] = 20;
a[2] = 30;
cout << a << endl;
Array<int> b = a; // 拷贝构造
cout << b << endl;
Array<int> c;
c = b; // 拷贝赋值
cout << c << endl;
Array<string> d;
d[0] = "北京";
d[1] = "上海";
d[2] = "广州";
cout << d << endl;
Array<Array<int> > e;
for (int i = 0; i < e.size (); ++i)
for (int j = 0; j < e[i].size ();++j)
e[i][j] = (i+1)*10+j+1;
cout << e << endl;
Array<Array<Array<int> > > f;
int const /*volatile*/ col = 4;
Array<Array<int, col>, 1+1+1> g;
for (int i = 0; i < g.size (); ++i)
for (int j = 0; j < g[i].size ();++j)
g[i][j] = (i+1)*10+j+1;
for (int i = 0; i < g.size (); ++i) {
for (int j = 0; j < g[i].size ();++j)
cout << g[i][j] << ' ';
cout << endl;
}
return 0;
}

3.typename关键字

1)声明模板的类型形参
template<typename T> ...
typename意在说明其后的标示符T表示一个类型形参。这样的typename也可以被替换为class关键字。
2)解决嵌套依赖
在一个类模板或者函数模板中,有时候需要引用某种类型的嵌套类型(内部类型),但是包装该嵌套类型的外部类型又依赖于该模板的类型参数,这种现象被称为嵌套依赖。一旦出现嵌套依赖,编译器在第一次编译该模板时,因为无法确定依赖于类型参数的外部类型,就会将嵌套于该外部类型的内部类型假定为其静态成员变量,导致编译失败。这时,为这些被引用的嵌套类型加上typename关键字,意在告诉编译器,它们不是静态成员变量,而是某种类型,可以用于其它变量的声明。这样一来,编译器就会把有关嵌套类型的检查工作推迟到第二次编译时完成,避免编译错误。

#include <iostream>
using namespace std;
template<typename T>
class A { // 包装类/外部类
public:
    class B { // 嵌套类/内部类
    public:
        T m_var;
    };
    T m_var;
};
template<typename T>
void foo (T arg) {
    A<T> a;
    cout << sizeof (a) << endl; // 4
    typename A<T>::B b; // 嵌套依赖
    cout << sizeof (b) << endl; // 4
}
template<typename T>
class Z {
public:
    typename A<T>::B m_var; // 嵌套依赖
};
int main (void) {
    A<int> a;
    cout << sizeof (a) << endl; // 4
    A<int>::B b;
    cout << sizeof (b) << endl; // 4
    foo (100);
    Z<int> z;
    cout << sizeof (z.m_var) << endl;// 4
};

 

struct \
class - 声明类
\ 声明模板的
/ 类型参数
typename - 解决嵌套依赖
4.template关键字
1)声明函数模板或类模板
template<...> ...

2)解决嵌套模板
在一个函数模板或类模板中,引用某个依赖于模板参数的模板类型的内部模板是,会因为编译器无法识别外部模板的类型而导致编译失败。通过template关键字可以向编译器显式指明所引用的内部标识是一个模板,避免编译器对模板参数表产生误解,解决编译错误。

#include <iostream>
#include <typeinfo>
using namespace std;
template<typename A>
class X {
public:
    // 类模板中的函数模板
    template<typename B>
    void foo (void) const {
        B var;
        cout << typeid (var).name ()
            << ' '
            << typeid (m_var).name ()
            << endl;
    }
    // 类模板中的类模板
    template<typename B>
    class Y {
    public:
        B m_var;
    };
    A m_var;
};
template<typename A, typename B>
void bar (void) {
    X<A> x, *px = &x;
    x.template foo<B> (); // d i
    px->template foo<B> (); // d i
    typename X<A>::template Y<B> y;
}
int main (void) {
    X<int> x;
    x.foo<double> (); // d i
    X<int>::Y<double> y;
    bar<int, double> ();
    return 0;
}

 


5.在子类模板中访问基类模板的成员
在子类模板中访问那些在基类模板中声明,且依赖于模板参数的符号(成员变量、成员函数、成员类型等),应该在它们前面加上作用域限定符或this->,否则编译器将在全局域而非基类域中寻找所引用的符号,导致编译失败或结果错误。

include <cstdlib>
#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T>
class Base {
public:
int m_var; // 成员变量
void foo (void) { // 成员函数
cout << m_var << endl;
}
class Type {}; // 成员类型
void exit (int status) {
cout << "再见!" << endl;
}
};
template<typename T>
class Derived : public Base<T> {
public:
void bar (void) {
//    Base<T>::m_var = 100;
this->m_var = 100;
//    Base<T>::foo ();
this->foo ();
typename Base<T>::Type var;
cout << typeid (var).name ()
<< endl;
//    Base<T>::exit (0);
this->exit (0);
}
};
int main (void) {
Derived<int> d;
d.bar ();
return 0;
}

 

6.内部模板的外部定义

#include <iostream>
#include <typeinfo>
using namespace std;
template<typename A>
class X {
public:
// 类模板中的类模板
/*
template<typename B>
class Y {
public:
// 类模板中的类模板中的函数模板
template<typename C>
void foo (void) const {
C var;
}
B m_var;
};
*/
template<typename B> class Y;
A m_var;
};
template<typename A>
template<typename B>
class X<A>::Y {
public:
template<typename C>
void foo (void) const;
B m_var;
};
template<typename A>
template<typename B>
template<typename C>
void X<A>::Y<B>::foo (void) const {
C var;
cout << typeid (var).name () << endl;
}
int main (void) {
X<int> x;
cout << typeid (x.m_var).name ()
<< endl; // i
X<int>::Y<double> y;
cout << typeid (y.m_var).name ()
<< endl; // d
y.foo<string> (); // Ss
return 0;
}


7.模板型模板参数
如果一个类模板或者函数模板的模板参数所结合的实参不是一个具体类型而是一个模板,那么该形参不能用typename关键字声明,而必须使用所结合模板实参的原型进行声明。

#include <iostream>
using namespace std;
// 数组模板
template<typename T>
class Vector {
public:
void push_back (T const& data) {
cout << "向数组尾端压入数据"
<< endl;
}
void pop_back (void) {
cout << "从数组尾端弹出数据"
<< endl;
}
};
// 链表模板
template<typename T>
class List {
public:
void push_back (T const& data) {
cout << "向链表尾端压入数据"
<< endl;
}
void pop_back (void) {
cout << "从链表尾端弹出数据"
<< endl;
}
};
// 堆栈模板
template<typename T,
template<typename> class C>
class Stack {
public:
// 压入
void push (T const& data) {
m_c.push_back (data);
}
// 弹出
void pop (void) {
m_c.pop_back ();
}
private:
C<T> m_c;
//    C m_c;
};
int main (void) {
//    Stack<int, Vector<int> > siv;
Stack<int, Vector> siv;
siv.push (100);
siv.pop ();
//    Stack<int, List<int> > sil;
Stack<int, List> sil;
sil.push (100);
sil.pop ();
return 0;
}

 

8.零初始化
1)基本类型不存在缺省构造函数,未被显式初始化的局部变量都具有一个不确定的值。
int var; // 未初始化(不确定的值)
2)包含(自定义的或系统提供)缺省构造函数的类,在未被显式初始化的情况下,都会有一个确定的缺省初始化状态。
Stduent var; // 缺省初始化(调用缺省构造函数)
3)在模板的实现中就会产生不一致的语法语义:
T var; // T是基本类型,var没初始化
// T是类类型,var缺省初始化
4)为了使模板对不同类型表现出的一致性,就需要采用零初始化语法:
T var = T ();
T=int -> int var = int (); // 和类型对应的零初始化
T=string -> string var = string (); // 缺省构造

#include <iostream>

using namespace std;
template<typename T>
void foo (void) {
T var = T ();
cout << var << endl;
}
template<typename T>
class A {
public:
A (void) : m_var () {
//    m_var = T ();
}
void foo (void) const {
cout << m_var << endl;
}
private:
T m_var;
};
int main (void) {
foo<int> ();
foo<double> ();
foo<int*> ();
foo<string> ();
A<int> a1;
a1.foo ();
A<double> a2;
a2.foo ();
A<int*> a3;
a3.foo ();
A<string> a4;
a4.foo ();
return 0;
}

 

9.类模板和虚函数
1)在类模板中可以声明虚函数,而且也可以表现出多态性,但是在该模板的实例化过程中不能违背虚函数有效覆盖的条件,否则将丧失多态性,甚至可以引发编译错误。
2)模板函数不能被声明为虚函数。基于虚函数的多态机制,需要一个名为虚函数表的函数指针数组。该数组在类被编译或类模板被实例化的过程中产生,而此时那些模板形式的成员函数尚未被实例化,其入口地址和重载版本的个数,要等到编译器处理完对该函数的所有调用以后才能确定。成员函数模板的延迟编译阻碍了虚函数表的静态构建。
3)广义多态:通过一种类型的一种方法表现出多种不同的行为。
A.动态多态,基于虚函数和动态绑定的多态。
B.静态多态,基于类模板和类型参数的多态。

#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw (void) const = 0;
};
class Rect : public Shape {
public:
void draw (void) const {
cout << "绘制矩形" << endl;
}
};
class Circle : public Shape {
public:
void draw (void) const {
cout << "绘制圆形" << endl;
}
};
// 多态函数
void drawAny (Shape const& shape) {
shape.draw ();
}
int main (void) {
Rect r;
Circle c;
drawAny (r);
drawAny (c);
return 0;
}
spoly.cpp

#include <iostream>
using namespace std;
class Rect {
public:
void draw (void) const {
cout << "绘制矩形" << endl;
}
};
class Circle {
public:
void draw (void) const {
cout << "绘制圆形" << endl;
}
};
// 多态函数
template<typename Shape>
void drawAny (Shape const& shape) {
shape.draw ();
}
int main (void) {
Rect r;
Circle c;
drawAny (r);
drawAny (c);
return 0;
}

 

 

10.编译模型
1)单一模型:将模板的声明、实现和实例化放在同一个源文件中。
优点:编译简单。
缺点:源文件规模较大,不易于维护,不易于协作开发。

2)分离模型:将模板的声明、实现和实例化分别放在不同的源文件中。
优点:源文件规模可控,易于维护和协作开发。
缺点:链接障碍。
参见:div/

// 声明模板
#ifndef _CMP_H
#define _CMP_H
template<typename T>
T max (T x, T y);
template<typename T>
T min (T x, T y);
template<typename T>
class Comparator {
public:
    Comparator (T x, T y);
    T max (void) const;
    T min (void) const;
private:
    T m_x, m_y;
};
#endif // _CMP_H


// 实现模板
#include "cmp.h"
template<typename T>
T max (T x, T y) {
    return x < y ? y : x;
}
template<typename T>
T min (T x, T y) {
    return x < y ? x : y;
}
template<typename T>
Comparator<T>::Comparator (T x, T y) :
    m_x (x), m_y (y) {}
template<typename T>
T Comparator<T>::max (void) const {
    return m_x < m_y ? m_y : m_x;
}
template<typename T>
T Comparator<T>::min (void) const {
    return m_x < m_y ? m_x : m_y;
}

 

3)包含模型:在模板的声明文件尾部包含该模板的实现文件,促使预编译器将模板的声明、实现和实例化代码放到同一个编译单元中,避免分离模型因为模板代码得不到二次编译而遇到的链接障碍。
优点:链接成功。
缺点:延长编译时间,模板的实现源码必须公开。
参见:inc/

// 声明模板
#ifndef _CMP_H
#define _CMP_H
template<typename T>
T max (T x, T y);
template<typename T>
T min (T x, T y);
template<typename T>
class Comparator {
public:
    Comparator (T x, T y);
    T max (void) const;
    T min (void) const;
private:
    T m_x, m_y;
};
#include "cmp.cpp"
#endif // _CMP_H

// 实现模板
template<typename T>
T max (T x, T y) {
    return x < y ? y : x;
}
template<typename T>
T min (T x, T y) {
    return x < y ? x : y;
}
template<typename T>
Comparator<T>::Comparator (T x, T y) :
    m_x (x), m_y (y) {}
template<typename T>
T Comparator<T>::max (void) const {
    return m_x < m_y ? m_y : m_x;
}
template<typename T>
T Comparator<T>::min (void) const {
    return m_x < m_y ? m_x : m_y;
}


// 实例化模板
#include <iostream>
using namespace std;
#include "cmp.h"
int main (void) {
    int a = 123, b = 456;
    double c = 1.3, d = 4.6;
    string e = "hello", f = "world";
    cout << ::max (a, b) << ' '
        << ::min (a, b) << endl;
    cout << ::max (c, d) << ' '
        << ::min (c, d) << endl;
    cout << ::max (e, f) << ' '
        << ::min (e, f) << endl;
    Comparator<int> ci (a, b);
    cout << ci.max () << ' '
        << ci.min () << endl;
    Comparator<double> cd (c, d);
    cout << cd.max () << ' '
        << cd.min () << endl;
    Comparator<string> cs (e, f);
    cout << cs.max () << ' '
        << cs.min () << endl;
    return 0;
}

 

4)实例模型:在模板实现文件中以显式实例化的方式,强制编译器提前对所实现的模板做二次编译,解决分离模型的链接问题。
优点:不会延长编译时间,模板的实现源码可以不公开。
缺点:类型受限,不适于构建通用模板库。
参见:ins/

// 声明模板
#ifndef _CMP_H
#define _CMP_H
template<typename T>
T max (T x, T y);
template<typename T>
T min (T x, T y);
template<typename T>
class Comparator {
public:
    Comparator (T x, T y);
    T max (void) const;
    T min (void) const;
private:
    T m_x, m_y;
};
#endif // _CMP_H

// 实现模板
#include <string>
using namespace std;
#include "cmp.h"
template<typename T>
T max (T x, T y) {
    return x < y ? y : x;
}
template<typename T>
T min (T x, T y) {
    return x < y ? x : y;
}
template<typename T>
Comparator<T>::Comparator (T x, T y) :
    m_x (x), m_y (y) {}
template<typename T>
T Comparator<T>::max (void) const {
    return m_x < m_y ? m_y : m_x;
}
template<typename T>
T Comparator<T>::min (void) const {
    return m_x < m_y ? m_x : m_y;
}
template int max<int> (int, int);
template int min<int> (int, int);
template double max<double> (double,
    double);
template double min<double> (double,
    double);
template string max<string> (string,
    string);
template string min<string> (string,
    string);
template class Comparator<int>;
template class Comparator<double>;
template class Comparator<string>;


// 实例化模板
#include <iostream>
using namespace std;
#include "cmp.h"
int main (void) {
    int a = 123, b = 456;
    double c = 1.3, d = 4.6;
    string e = "hello", f = "world";
    cout << ::max (a, b) << ' '
        << ::min (a, b) << endl;
    cout << ::max (c, d) << ' '
        << ::min (c, d) << endl;
    cout << ::max (e, f) << ' '
        << ::min (e, f) << endl;
    Comparator<int> ci (a, b);
    cout << ci.max () << ' '
        << ci.min () << endl;
    Comparator<double> cd (c, d);
    cout << cd.max () << ' '
        << cd.min () << endl;
    Comparator<string> cs (e, f);
    cout << cs.max () << ' '
        << cs.min () << endl;
    return 0;
}

5)导出模型:通过export关键字将模板声明为导出,迫使编译器将被导出模板的内部表示保存在目标文件中,待到链接阶段再补充二次编译的过程,顺利通过链接。
优点:不会延长编译时间,模板的实现源码可以不公开,类型不受限制。
缺点:绝大多数的C++编译器都不支持导出模型。
GNU/Microsoft/IBM/Oracle/HP
Edison Design Ltd.
C++2011将导出模型删除了!

11.预编译头
将一些公共头文件和含有模板实现的文件包含在一个独立的头文件中:
common.h
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include "cmp.h"
编译该头文件:
g++ -c common.h -> common.h.gch

任何包含common.h代码都可以用加载预编译头文件common.h.gch内容的方式代替对头文件的扩展和编译,从而缩短编译时间。
微软:
afxstd.h
afxstd.cpp -> <项目>.pch
注意,任何对预编译头文件的修改都必须反映到编译结果中,否则修改无效。

posted @ 2018-03-29 10:30  Truman001  阅读(359)  评论(0编辑  收藏  举报