模板与泛型编程 c++ primer ch16.1

在摸板定义中,模板参数列表不能为空,

编译器用推断出的参数来进行 实例化(instantiation)

一般来说 模板是有type parameter
但是也可以声明 nontype parameter

template<unsgned N,unsigned M>
int compare(const char(&p) [n], const char(&p2)[M]){
    return strcmp(p1,p2);
}

compare("hi","mom")
在执行时 ,实际上 实例化(instantiaton) 出的是 int compare(const char (&p)[3] ...
因为编译器会在字符串常量的末尾添加一个空字符作为终结符

  • 一个非类型参数(nontype patameter) 可以是一个整形,或者是一个指向对象或函数类型的 指针 或者 (左值)引用
  • 绑定到 非类型整形参数的实参 必须是一个常量表达式
  • 绑定 指针或引用 的非类型参数 实参必须具有静态的生存期
  • 不能使用一个普通(非 static ) 的局部变量或动态对象 作为 指针或引用 非类型模板参数的实参。
  • 指针参数可以使用nullptr 或者 一个值为0 的常量表达式 来进行 实例化

+模板定义内 nontype parameter 是一个常值量 ,可以使用在需要长治表达式的地方 比如指定数组 的大小

可以声明为 inline 或者 constexpr 在模板参数列表之后 返回类型之前

编写泛型代码的两个重要原则###

  • 模板中函数参数是const 引用
  • 函数体的判断中仅仅使用了 < 比较操作

通过将函数参数设置为const 的引用 ,我们就保证了函数可以用于不能拷贝的对象上面

调用函数的时候 编译器需要掌握函数的声明,
所以函数声明和类定义都放在头文件里面。而函数的方法体与类的成员函数可以放在源文件中

模板则不同,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义,因此 模板的头文件 通常既包括声明也包括定义

模板大多到实例化期间才会 有编译错误
这主要在三个阶段报告错误

  • 编译模板本身时,这一阶段检查语法错误
  • 遇到模板使用时,检查参数匹配是否正确
  • 模板实例化时 ,这一阶段发现类型相关的错误,可能在连接的时候才报告

编译器不能为类模板 推断模板参数类型 必须在模板名后的尖括号提供额外的信息---- 用来代替模板参数的模板实参列表
下面是一个列子

template <typename T>  class Blob{
    public:
        typedef T value_type;
        typedef_ typename std::vector<T>::size_type size_type;   //这里 是说编译器在实例化之前 是不知道 vector<T>::size_type 是什么东西
                                                                                                    //type_name 意在消除歧义,告诉编译器 size_type是一个类,而非一个成员变量
                                                                                                    // 而事实上有三种可能 静态数据成员 
                                                                                                    //                   静态成员函数     
                                                                                                    //                   嵌套类
        Blob();
        Blob(initializer_list<T> il);                                                   // 这里是支持了 initializer 的语法的
        size_type size()const{ return data->size() ; }
        bool empt() const {return data->empty(); }
        void push_back(const T& t)const {data->push_back(t);}
        void push_back(T &&t)const {data->push_back(std::move(t));}
        void pop_back();
        
        T& back();
        T& operator[](size_type i);

    private:
        std::shared_ptr<std::vector<T>> data;
        void check(*size_type i,const std::string &msg)const;
}

类模板的名字不是一个类型名,类模板用来实例化类型,而一个实例化的类型是包含模板参数的

定义在类模板内的成员函数被隐式声明为内敛参数 下面是方法体的定义

template<typename T>
void Blob<T>::check(size_type i,const std::string * msg)const {
    if(i>=data->size()){
        throw std::out_of_range(msg);
    }
}
template<tyepname T>
void Blob<T>::back()const {
    check(0,"back on empty Blob");
    return data->back();
}
template<typename T>
T& Blob<T>::operatpor[](size_type i){
    check(1,"subscript out of range");
    return (*data)[i];
}

在原 strBlob 中 下表运算符返回string& 而模板办吧呢

构造函数

Template<typename T>
Blob<T>::Blob():data(std::make_shared<std::vector<T>>()) {}  //大概是分配了一个vector 指针 然后将vector 指针存贮在 data中

Template<typename T>
Blob<T>::Blob(initializer_list<T> il):data(std::make_shared<std::vector<T>>(il){} // 原理大概同上

默认情况下 ,一个类模板的成员只有在使用的时候才会实例化

这一特性使得即使某种类型不能完全符合模板操作的要求,我们仍然可以用该类型实例化类

在类代码内可以简化模板类名的使用

当我们使用类模板类型是 必须提供模板实参, 但是有一点例外
就是在类模板 自己的作用域中时

template class BlobPtr{
public:
BlobPtr():curr(0){}
BlobPtr(Blob &a,size_t sz=0):wptr(a.data),curr(sz){}
T & operator() const {
auto P=check(curr,"dereference Past end);
return (
P)[curr]
}
BlobPtr & operator++();// 前置运算符
BlobPtr & operator--();// 这里的区别就是没有乐 模板参数 T乐因为在类的作用于内
private:
std::shared_ptr<std::vector> check(std::size_t,std::string&)const;
使用的是weak_ptr 表示底层的vector 可能被销毁
std::weak_ptr<std::vector> wptr ;
std::size_t curr;// 表示当前的位置
}
但是在函数体外部就必须加上模板参数乐
template
BlobPtr BlobPtr::operator++(int){
BlobPtr ret=this;// 这里的BlocPtr 并没有加上模板参数,是因为已经进入到了方法题的内部,所以不用使用参数了
++
this;
return ret;
}

##类模板与友元##
类模板包含一个非模板友元, 友元可以访问所有的模板实例
如果友元自身是模板,泪可以授权给所有的友元模板 也可以只授权给特定的实例?


###一对一友好关系###

template class Blob;
template class BlobPtr;
template bool operator==(const Blob& ,const Blob &);
//以上是生命有远的一些声明前置

template class Blob{
firend class Blob ;
firend bool operator==(const Blob&,const Blob &);
}

###通用和特定的友好关系###

template class Pal; //前置声明

class C{
firend class pal; 使用c 生成的模板类属于C的友元

template class pal ; 这样 全部的模板类都是C的友 并且这种情况不需要前置声明
}

templateclass C2{
friend class pal; 一对一
template friend class pal; 一对多
}


**令模板自己的类型参数成为友元** 
template <typename Type> class Bar{
    friend Type;
    //这样操作
}
**模板类型别名的使用方法**

template using twin = pair<T,T>
twin authors; 这个authors 是一个 pair<int,int>类型的变量
twin area 同理
还可以固定一个或者多个模板参数变量
template using twins= pair<T,unsiged>
twins books; 这里books 是一个pair<sting,unsigned> 类型的变量

类模板的静态成员
每一个模板类的实例中共享静态成员变量
vector<int> 中有一份静态变量
vector<double> 中也有一份静态变量

静态成员 变量的初始化
template<typename T>size_t Foo<T>:: ctr=0;
定义并且初始化ctr( 这是一个静态成员变量)

类似其余的成员变量,只有使用到的时候才会实例化成员变量或者是方法


模板参数遵循一般的变量规律,但是不能在模板内使用相同的模板参数名
 
###一个特定文件所需的所有模板的声明 通常一起放置在一个文件开始位置,出现于任何使用这些模板的代码之前。###


使用类的类型成员,编译器遇见
T::size_type *p;
要知道 T::size_type 是一个类型还是一个变量, 是类型就是声明,是变量就是相乘

编译器默认是认为是一个变量 所以如果是类型的化 需要显式的声明
下面是一个例子

template
typename T::value_type top( const T& c){
if(!c.empty){
return c.back();
}else{
return typename T::value_type();
}
}

上面的例子有值就返回值,没有就创建一个新的默认值对象返回

要通知编译器是一个类型的时候一定要使用typename 而不是class
##默认模板实参##
我们可以为函数和类模板提供默认实参,更早的版本中只有类模板可以
下面是一个函数对象模板的例子

template <typename T,typename F=less>
int compare(const &T&v1,cosnt T&v2,F f=F()){
if(f(v1,v2))return -1;
if(f(v2,v1))return 1;
return 0;
}

使用这个版本的compare,可以提供自己的比较器,但是并不是必须的,默认将使用 less<T> 

可以使用空出的尖括号说明采用的都是 默认的类型参数
##成员模板##
成员模板不能是虚的
###普通类(非模板类)的成员模板###
下面的例子是一个关于删除器的例子

class DebugDelete{
public:
DebugDelete(std::ostream &s=std::cerr;
templatevoid operator()(T* p)const{
os<<"deleting unique_ptr"<<std::endl;
delete p;
}
private:
std::ostream &os;
}

// 上述定义的对象可以用作于 delete 相同的表现 ,删除任意类型的对象
###模板类的成员模板###

template class Blob{
template Blob (It b,It e);
}

要注意的是这样定义的成员模板在类外声明的时候必须提供两个的模板参数列表 如下
template template Blob (It a,It b):data(std::make_shared<std::vector>(b,e){} //像这样


与之前一致,我们在那个对象上调用了成员模板,就使用那块的参数进行推断并且实例化 initiation 

##控制实例化##
在较大的系统中,多个源文件实例化同一个模板的代价非常的高,我们可以使用显式实例化来避免这种问题
explicit instanitaiton 如下

extern template declaration;// 实例化声明
template declaration; // 实例化定义
比如
extern template class Blob;
extern template int compare(const int&,const int&) ;

上面的定义和声明说明(承诺了) 在其他地方有一个已经初始化了的代码
,对于一个给定的实例化版本,可以有多个extern ,但必须只有一个定义

extern 必须出现在任何使用实例化版本的代码之前

Application.o 将包含Blob<int> 的实例化版本 ,但是不会包括Bolb<string> 
的,如果另外一个文件templateBuld 包括 ,那在连接的时候就要将 templateBulid.o 同Application.o 进行连接

与普通方式不同,我们进行显式实例化时候,必须能够实例化类模板中的所有成员
原文地址:https://www.cnblogs.com/sfzyk/p/8563656.html