C++模板编程(3)---模板编程基本技巧Tricky Basics

  • 关键词typename的另一种用途
  • 将成员函数和嵌套类作为模板的模板模板参数template template paramters
  • 零值初始化zero Initialization
  • 字符串字面常量string literals作为模板参数

1. 关键词 typename

关键词typename是C++标准化过程中引入的,目的在向编译器说明template内的某个标识符是类型(而不是其他)。考虑如下例子:

template <typename T>

class MyClass {

typename T::SubType * ptr;

...

};

在这里,第二个typename关键词的意思是SubType是class T内部定义的一个类型,从而ptr是一个

“指向T::SubType”的指针。

如果上面没有使用关键词typename,SubType会被认为是class T的一个static成员,于是编译器理解一个具体变量或一个对象,导致下面的语句:

T::SubType * ptr;

表达的意义变成:class T的一个静态static成员SubType与ptr相乘。

通常如果某个与template parameter相关的名称是个类型type时,就必须加上关键词typename。

1.1 迭代器应用

typename的一个典型应用是在template程序代码中使用STL容器供应的迭代器iterators:

#include <iostream>

template <typename T>

void printcoll(T const& coll)

{

        typename T::const_iterator pos;

        typename T::const_iterator end(coll.end());

        for(pos = coll.begin(); pos != end; ++pos) {

               std::cout << *pos <<  '  ';

        }

        std::cout << std::endl;

}

    在这个函数模板中, coll是个STL容器,其元素类型为T。这里使用了STL容器的迭代器类型iteration type遍历coll中的所有元素。迭代器类型为const_iterator,每个STL容器都声明有这个类型:

class stlcontainer {

        ...

        typedef        ...        iterator;

        typedef         ...        const_iterator;

        ...

};

使用模板类型template Type T的const_iterator时,必须写出全民并在最前面加上关键词typename:

typename T::const_iterator pos;

1.2 .template 结构体(construct)

考虑如下程序,其中使用标准的bitset类型:

template <int N>

void printBitset(std::bitset<N> const& bs)

{

        std::cout << bs.template to_string<char, char_traits<char>, allocator<char> >();

}

此例子中的 .template看起来古怪,但是如果没有它,编译器无法得知紧跟其后面的 “<”是template argument list的起始,而非一个小于号。这里的参数bs依赖于模板参数template paramter N。

结论是“.template” 或"->template"记号只在templates之内才能被使用,且必须紧跟着与模板参数相关的东西。

2 .使用this指针
如果class template拥有base class,那么其内出现的成员名称x并非总是等价与this->x,即使x系继承而来。例如:

template <typename T>

class Base {

        public:

                void exit();

};

template <typename T>

class Derived: public Base<T> {

        public:

                void foo() {

                       exit();

                }

};

本例中,foo()内解析exit符号时,定义于Base的exit会被编译器忽略。因此,要么获得一个编译器错误,要么是调用外部额外写的exit()函数。

目前避免这样的情况有一个准则:

使用与template相关的符号时,建议总是以this->或Base<T>::进行修饰。

3.成员模板Member Templates

    class的成员既可以是templates:既可以是nested class templates,也可以是member function templates。通常只有当两个stack类型相同,也就是当两个stacks拥有相同类型的元素时,你才能对它们相互赋值assign,也就是将某个stack赋值给另一个。不能把某种类型的stack赋值给另一种类型的stack,即使这两种类型之间可以隐式转型:

Stack<int> intStack1, intStack2;

Stack<float> floatStack;

...

intStack1 = intStack2;    //OK

floatStack = intStack1;  //ERROR: 两个stacks类型不同

然而,可以把assignment运算符定义为一个template,就可以把两个“类型不同,但其元素可以隐式转型的”堆栈互相赋值。为完成此时,Stack<>需要如下面声明:

template <typename T>

class Stack {

private:

        std::deque<T> elems;

public:

        void push(T const &);

        void pop();

        T top() const;

        bool empty() const {

               return elems.empty();

         }

        template <typename T2>

        Stack<T>& operator= (Stack<T2> const&)

}

 相比原先的Stack,有如下改动:

1)增加一个assignment运算符,使Stack可被赋予一个拥有不同元素类型T2的Stack.

2)这个Stack使用Deque作为内部容器。

具体新增加的assignment运算符定义实现如下:

template <typename T>

template <typename T2>

Stack<T>& Stack<T>::operator=  (Stack<T2> const& op2)

{

        if((void*)this == (void*) &op2) {

                return *this;

        }

        Stack<T2> tmp(op2);

        elems.clear();

        while(!tmp.empty()){

               elems.push_front(tmp.top());

                tmp.pop();

        }

        return *this;

}

在拥有template parameter T的模板中定义一个内层的inner模板参数template parameter T2:

template <typename T>

template <typename T2>

...

现在有了这个赋值模板成员member template,就可以把一个int stack赋值给一个float stack:

Stack<int> intStack1, intStack2;

Stack<float> floatStack;

...

floatStack = intStack1;

这看起来绕过了类型检查,其实必要的类型检查还是有的,发生在源端的元素拷贝到目的端时进行:elems.push_front(tmp.top());

这里会将整数类型的元素转换成浮点类型元素。

因此试图将字符堆栈转换成浮点堆栈是不能成功的,因为string不能转换成float类型。

前面的模板赋值运算符并不取代default 赋值运算符。如果你在相同类型的Stack之间赋值,编译器还是会采用default assignment运算符。

这里,我们也可以把内部容器参数化:

template <typename T, typename CONT= std::deque<T> >

Class Stack{

private:

        CONT elems;

public:

        void push(T const&);

        void pop();

        T top() const;

        bool empty() const {

               return elems.empty();

        }

        template <typename T2, typename CONT2>

        Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);

};

此时,template assignment运算符实现如下:

template <typename T, typename CONT>

template <typename T2, typename CONT2>

Stack<T,CONT>&     Stack<T,CONT>::operator= (Stack<T2, CONT2> const& op2)

{

        if((void*)this == (void*)&op2) {

               return *this;

        }

        Stack<T2, CONT2> tmp(op2);

        elems.clear();

        while(!tmp.empty()) {

                elems.push_front(tmp.top());

                tmp.pop();

        }

        return *this;

}

4. 模板模板参数(双模板参数)Template Template parameters

    一个模板参数template parameter本身也可以是个类模板class template,这就是(类)模板(的)模板参数的由来,类模板的模板参数非常有用。

为了使用其他类型的元素容器,stack class使用者必须两次知道元素类型,一次是元素类型本身,另一次是容器类型:

Stack<int, std::vector<int> > vStack;

但是如果使用模板模板参数,就可以仅仅指明元素类型,无须再指定容器类型:

Stack<int, std::vector> vStack;

为了实现这种特性,你必须把第二个模板参数template parameter声明为模板(的)模板参数。

原则上代码可以写为:

template <typename T,

                 template <typename ELEM> class CONT = std::deque >

Class Stack {

private:

        CONT<T> elems;

public:

        void push(T const&);

        void pop();

        T top() const;

        bool empty() const {

                return elems.empty();

        }

};

与先前的差别是,第二个模板参数被声明为一个类模板class template,上面黑体部分,

template <typename ELEM>  class CONT

其默认值也由原先的std::deque<T> 变更为std::deque. 这个参数必须是个class template,并以第一参数的类型完成实例化:

CONT<T> elems;

但是这样编译还是有问题,这就是:

模板模板参数的匹配问题。

本例的问题在于:标准库中的std::deque template要求不只是一个参数:第二个参数是个配置器allocator,它虽然有默认值,但当它被用来匹配CONT的参数时,其默认值就被编译器强行忽略了。我们可以重写class声明语句。使得CONT参数要求带两个参数的容器:

template <typename T,

                template <typename ELEM,

                                typename ALLOC = std::allocator<ELEM> >

                                class CONT = std::deque>

Class Stack {

 private:

        CONT<T>  elems;

        ...

};

最后的Stack template实现如下。

1)template2stack.hpp

#ifndef TEMPLATE2STACK_H
#define TEMPLATE2STACK_H
 
#include <deque>
#include <stdexcept>
#include <memory>
 
template <typename T,
          template <typename ELEM,
                    typename = std::allocator<ELEM> >
                    class CONT = std::deque>
class Template2Stack{
private:
    CONT<T> elements;
public:
    void push(T const&);
    void pop();
    T top() const;
    bool empty() const {
        return elements.empty();
    }
 
    template<typename T2,
             template <typename ELEM2,
                       typename = std::allocator<ELEM2> >
                        class CONT2>
    Template2Stack<T, CONT>& operator= (Template2Stack<T2, CONT2> const&);
};
 
template <typename T, template <typename, typename> class CONT>
void Template2Stack<T,CONT>::push(T const& elem)
{
    elements.push_back(elem);
}
 
template <typename T, template <typename, typename> class CONT>
void Template2Stack<T,CONT>::pop()
{
    if(elements.empty()){
        throw std::out_of_range("template2Stack<>::pop(): empty stack");
    }
    elements.pop_back();
}
 
template <typename T, template <typename, typename> class CONT>
T Template2Stack<T,CONT>::top() const
{
    if(elements.empty()){
        throw std::out_of_range("template2Stack<>::top(): empty stack");
    }
    elements.back();
}
 
template <typename T, template <typename, typename> class CONT>
template <typename T2, template <typename, typename> class CONT2>
Template2Stack<T,CONT>&
Template2Stack<T,CONT>::operator= (Template2Stack<T2,CONT2> const& op2){
    if((void*)this == (void*) &op2){
        return *this;
    }
 
    Template2Stack<T2, CONT2> tmp(op2);
 
    elements.clear();
    while (!tmp.empty()) {
        elements.push_front(tmp.top());
        tmp.pop();
 
    }
    return *this;
}
 
#endif // TEMPLATE2STACK_H
 

2) stacktest.cpp

#include <QCoreApplication>
#include <iostream>
#include <string>
#include <cstdlib>
#include <vector>
#include <template2stack.h>
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
 
    try {
        Template2Stack<int,std::deque> intStack;
        Template2Stack<float,std::deque> floatStack;
 
        intStack.push(42);
        intStack.push(7);
 
        floatStack.push(7.7);
        floatStack.push(42.42);
 
        floatStack = intStack;
        std::cout << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << floatStack.top() << std::endl;
        floatStack.pop();
        std::cout << floatStack.top() << std::endl;
        floatStack.pop();
 
 
    } catch (std::exception const& ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }
 
    return a.exec();
}
 

5. 零值初始化

对于基本类型如int,double, pointer,type来说,并没有一个default构造函数将它们初始化为有意义的值,任何一个未初始化的区域变量(local variable),其值都是未定义的:

void foo()

{

        int x;    //x未定义

        int* ptr;  //ptr未定义

}

解决方法:

template <typename T>

void foo()

{

        T x= T();

}

6. 字符串常数作为模板参数

以引用方式by reference传递给函数模板参数,可能会有意想不到的错误

类型不匹配,编译不过。

 

7. 小结

    当要操作一个取决于模板参数的类型名称时,应该在其前面冠以关键词typename .嵌套类和成员函数也可以是templates.

    assignment运算符的template版本并不会取代default assignment运算符。

   可以把class templates作为template parameters使用,称为template template parameters.

   template template arguments 必须完全匹配其对应参数。预设的模板参数会被编译,忽略,特别要小心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值