首页 > 代码库 > C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]

C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]

模板与泛型编程

--模板定义[]

四、模板类型形参

类型形参由关键字classtypename后接说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。

模板类型形参可作为类型说明符在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。具体而言,它可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。

template <class T>
T calc(const T &a,const T &b)
{
    T tmp = a;
    //...
    return tmp;
}

1typenameclass的区别

在函数模板形参表中,关键字typenameclass具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:

template <typename T, class U> calc (const T&, const U&);

使用关键字typename代替关键字class指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename更清楚地指明后面的名字是一个类型名。但是,关键字typename是作为标准C++的组成部分加入到C++中的,因此旧的程序更有可能只用关键字class



2、在模板定义内部指定类型

除了定义数据成员或成员函数外,类还可以定义类型成员。例如,标准库的容器类定义了不同的类型,size_type,使我们能够以独立于机器的方式使用容器。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。例如,考虑下面的函数:

template <class Parm,class U>
Parm fcn(Parm *array,U value)
{
    Parm: :size_type *p;    	//如果Parm::size_type是一个类型,则该语句是一个声明
                            		//如果Parm::size_type是一个对象,则该语句是一个乘法运算
}

默认情况下,编译器假定这样的名字指定数据成员,而不是类型

如果希望编译器将size_type当作类型,则必须显式告诉编译器这样做:

template <class Parm,class U>
Parm fcn(Parm *array,U value)
{
    typename Parm::size_type *p;   //OK
}

通过在成员名前面加上typename作为前缀,可以告诉编译器将成员当做类型。通过编写typenameparm::size_type,指出绑定到Parm的类型的size_type成员是类型的名字。当然,这一声明给用实例化fcn的类型增加了一个职责:那些类型必须具有名为size_type的成员,而且该成员是一个类型

【最佳实践】

如果拿不准是否需要以typename指明一个名字是一个类型,那么指定它是个好主意。在类型之前指定typename没有害处,因此,即使typename是不必要的,也没有关系。

//P533 习题16.12
template <typename IterType>
typename IterType::value_type
mostApr(IterType first,IterType last)
{
    map<typename IterType::value_type,size_t> count;

    while (first != last)
    {
        ++ count[*first];

        ++first;
    }

    int apper = 0;
    typename IterType::value_type maxAprItem;

    for (typename map<typename IterType::value_type,size_t>::iterator
            iter = count.begin();
            iter != count.end(); ++iter)
    {
        if (iter -> second > apper)
        {
            maxAprItem = iter -> first;
            apper = iter -> second;
        }
    }

    return maxAprItem;
}

int main()
{
    vector<int> ivec;
    int val;

    ifstream inFile("input");
    while (inFile >> val)
    {
        ivec.push_back(val);
    }

    cout << mostApr(ivec.begin(),ivec.end()) << endl;
}

//习题16.13
template <typename ContainerType>
void printCntanr(ContainerType contnr)
{
    typename ContainerType::size_type n = 0;

    while (n != contnr.size())
    {
        cout << contnr[n ++] << endl;
    }
}

//习题16.14
template <typename IterType>
void printCntanr(IterType first,IterType last)
{
    while (first != last)
    {
        cout << *first << endl;
        ++first;
    }
}


五、非类型模板形参

模板形参不必都是类型。

在调用函数时非类型形参将用值代替值的类型模板形参表中指定

template <class T,size_t N>
void array_init(T (&parm)[N])
{
    for (size_t i = 0; i != N; ++i)
    {
        parm[i] = 0;
    }
}

模板非类型形参是模板定义内部常量值,在需要常量表达式的时候,可使用非类型形参指定数组的长度。

当调用array_init时,编译器从数组实参计算非类型形参的值

    int x[42];
    double y[10];

    array_init(x);	//T被int替代,N被42替代
    array_init(y);	//T被double替代,N被10替代

编译器将为array_init调用中用到的每种数组实例化一个array_init版本。



类型等价性与非类型形参

对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。比如下面的两个array_init调用引用的是相同的实例– array_init<int,42>

    int x[42];
    const int sz = 40;
    int y[sz + 2];

    //两个的函数实例相同
    array_init(x);
    array_init(y);


//P534 习题16.15/16
template <typename Type,std::size_t N>
std::size_t getArrSize(Type (&arr)[N])
{
    return N;
}

template <typename ArrType,std::size_t N>
void printArr(ArrType (&arr)[N])
{
    for (std::size_t i = 0; i != N; ++ i)
    {
        cout << arr[i] << endl;
    }
}

int main()
{
    int X[] = {3,34,5,65,67,7,7};
    cout << "array size is: " << getArrSize(X) << endl;
    cout << "array items:" << endl;
    printArr(X);
}

六、编写泛型程序

编写模板时,代码不可能针对特定类型,但模板代码总是要对将使用的类型做一些假设。例如,虽然compare函数从技术上说任意类型都是有效的,但实际上,实例化的版本可能是非法的

产生的程序是否合法,取决于函数中使用的操作以及所用类型支持的操作

    if (v1 < v2)
        return -1; // T类型的对象必须支持 <
    if (v1 > v2)
        return 1; // T类型的对象必须支持 >

如果用不支持<操作符的对象调用compare,则调用是无效的:

    Sales_item item1, item2;
    cout << compare(item1, item2) << endl;  //Error:该程序不能编译

【小心地雷】

在函数模板内部完成的操作限制了可用于实例化该函数的类型。程序员的责任是:保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用那些操作的环境中那些操作运行正常



编写独立于类型的代码

【最佳实践】

编写模板代码时,对实参类型的要求尽可能少是很有益的。

虽然简单,但它说明了编写泛型代码的两个重要原则:

1模板的形参是const引用

2函数体中的测试只用<比较

通过将形参设为const引用,就可以允许使用不允许复制的类型。而且,如果有比较大的对象调用compare,则这个设计还可以使函数运行得更快

比较下面两段程序:

template <typename Type>
int compare(const Type &val1,const Type &val2)
{
    if (val1 < val2)
        return -1;
    else if (val1 > val2)
        return 1;

    return 0;
}

与:

template <typename Type>
int compare(const Type &val1,const Type &val2)
{
    if (val1 < val2)
        return -1;
    else if (val2 < val1)
        return 1;

    return 0;
}

下面的程序可以减少对可用于compare函数的类型的要求,这些类型必须支持<,但不必支持>



【警告:链接时的编译时错误】

一般而言,编译模板,编译器可能会在三个阶段中标识错误:

第一阶段是编译模板定义本身时。在这个阶段中编译器一般不能发现许多错误,可以检测到诸如漏掉分号或变量名拼写错误一类的语法错误

第二个错误检测时间是在编译器见到模板的使用。在这个阶段,编译器仍没有很多检查可做。对于函数模板的调用,许多编译器只检查实参的数目类型是否恰当,编译器可以检测到实参太多或太少,也可以检测到假定类型相同的两个实参是否真地类型相同。对于类模板,编译器可以检测提供的模板实参的正确数目。

产生错误的第三个时间是在实例化的时候,只有在这个时候可以发现类型相关的错误。根据编译器管理实例化的方式,有可能在链接时报告这些错误

重要的是,要认识到编译模板定义的时候,对程序是否有效所知不多。类似地,甚至可能会在已经成功编译了使用模板的每个文件之后出现编译错误。只在实例化期间检测错误的情况很少,错误检测可能发生在链接时