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

本文详细介绍了C++中的模板使用技巧,包括typename关键字的作用,如何处理成员函数和嵌套类作为模板参数,零值初始化,以及字符串字面常量作为模板参数。特别讨论了typename在迭代器应用中的重要性,以及如何避免成员模板和基类成员的混淆。此外,文章还阐述了模板模板参数的概念,展示了如何实现赋值运算符的模板版本,允许不同类型的堆栈互相赋值。最后,提到了模板模板参数的匹配问题和解决办法。
  • 关键词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 必须完全匹配其对应参数。预设的模板参数会被编译,忽略,特别要小心。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值