• templates(2.1)


    Class Templates

    类别模板

    就像上一章所说的 functions 那样,classes 也可以针对一或多个类型被参数化。用来管理「各种 不同类型的元素」的 container classes(容器类别)就是典型例子。运用 class templates 你可以实 作出可包容各种类型的 container class。本章将以一个 stack class 作为 class templates

    /* The following code example is taken from the book
     * "C++ Templates - The Complete Guide"
     * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
     *
     * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
     * Permission to copy, use, modify, sell and distribute this software
     * is granted provided this copyright notice appears in all copies.
     * This software is provided "as is" without express or implied
     * warranty, and with no claim as to its suitability for any purpose.
     */
    #include <vector>
    #include <stdexcept>
    
    template <typename T>
    class Stack {
      private:
        std::vector<T> elems;     // elements
    
      public:
        void push(T const&);      // push element
        void pop();               // pop element
        T top() const;            // return top element
        bool empty() const {      // return whether the stack is empty
            return elems.empty();
        }
    };
    
    template <typename T>
    void Stack<T>::push (T const& elem)
    {
        elems.push_back(elem);    // append copy of passed elem
    }
    
    template<typename T>
    void Stack<T>::pop ()
    {
        if (elems.empty()) {
            throw std::out_of_range("Stack<>::pop(): empty stack");
        }
        elems.pop_back();         // remove last element
    }
    
    template <typename T>
    T Stack<T>::top () const
    {
        if (elems.empty()) {
            throw std::out_of_range("Stack<>::top(): empty stack");
        }
        return elems.back();      // return copy of last element
    }

    正如你所见,这个 class template 是以标准库的 class template vector<> 为基础实作出来的, 这样我们就可以不考虑内存管理、copy构造函数、assignment 运算符等等,从而得以把注意力放在这个 class template 的界面上。

    3.1.1 Class Templates 的声明

    声明class templates 的动作和声明function templates 类似:在声明语句之前加一条述句,以任意标识符声明类型参数(type parameters)。我们还是以T 作为标识符:

    template <typename T>
    class Stack {
      ...
    };

    相同的规则再次适用:关键词 class 可以代替关键词 typename

    template <class T>
    class Stack {
      ...
    };

    在 class template 内部,T 就像其它任意类型一样,可用来声明成员变量(member variables)和成员函数(member functions)。本例之中T 用来声明vector<> 所容纳的元素类型、声明一个「接受 T const&」的 push()函数、以及声明一个「返回类型为 T」的 top()函数:

    template <typename T>
    class Stack {
    private:
      std::vector
    <T> elems; // 元素 public:   Stack(); // 构造函数 void push(T const&); // push 元素 void pop(); // pop 元素 T top() const; // 传回最顶端的元素 };

    这个 class 的类型为 Stack<T>,T 是一个 template parameter。现在,无论何时你以这个class声明变量或函数时,都应该写成 Stack<T>。例如,假设你要声明自己的 copy 构造函数和assignment 运算符,可以写为:

    template <typename T>
    class Stack {
    ...
    Stack (Stack<T> const&); // copy 构造函数
    Stack<T>& operator= (Stack<T> const&); // assignment 运算符
    ...
    };

    然而如果只是需要class名称而不是class类型时,只需写Stack 即可。构造函数和析构函数的声明就属于这种情况。

    3.1.2 成员函数( Member Functions) 

    作为了定义 class template 的成员函数,你必须指出它是个 function template,而且你必须使用 class template 的全称。因此 Stack<T> 的成员函数 push()看起来便像这样:

    template <typename T>
    void Stack<T>::push (T const& elem)
    {
      elems.push_back(elem); // 将传入的元素 elem 附加于尾
    }

    这里调用了vector 的成员函数push_back(),把元素elem 追加到elems 尾端。注意,对一个vector进行 pop_back(),只是把最后一个元素移除,并不传回该元素。这种行为乃是基于异常安全性(exception safety)考虑。实现一个「移除最后元素并传回,而且完全顾及异常安全性」的 pop() 是不可能的( 这个问题最早由 Tom Cargill 在[CargillExceptionSafety]中 讨论过),[SutterExceptional] 条款 10 也有讨论)。然而,如果抛开可能的危险,我们可以实作出一个「移除最后元素并传回」的pop(),但必须声明一个类型为 T 的区域变量:

    template <typename T>
    T Stack<T>::pop ()
    {
    if (elems.empty()) {
    throw std::out_of_range("Stack<>::pop: empty stack");
    }
    T elem = elems.back(); // 保存最后元素的拷贝
    elems.pop_back(); // 移除最后一个元素
    return elem; // 传回先前保存的最后元素
    }

    当vector为空时,对它进行back()或pop_back()操作会导致未定义行为。所以我们必须在操作前先检查stack是否为空。如果stack为空,就抛出一个std::out_of_range 异常。top()之中也需进行相同检查;该函数传回 stack 的最后一个元素,但不移除之:

    template <typename T>
    T Stack<T>::top () const
    {
    if (elems.empty()) {
    throw std::out_of_range("Stack<>::top: empty stack");
    }
    return elems.back(); // 传回最后元素的拷贝
    }

    当然,你也可以把任何 class templates 的成员函数实作码写在 class 声明语句中,形成一个 inline式。例如:

    template <typename T>
    class Stack {
    ...
    void push(T const& elem) {
    elems.push_back(elem); // 将传入的元素 elem 附加于尾
    }
    ...
    };

    3.2 使用 Class Template Stack

    /* The following code example is taken from the book
     * "C++ Templates - The Complete Guide"
     * by David Vandevoorde and Nicolai M. Josuttis, Addison-Wesley, 2002
     *
     * (C) Copyright David Vandevoorde and Nicolai M. Josuttis 2002.
     * Permission to copy, use, modify, sell and distribute this software
     * is granted provided this copyright notice appears in all copies.
     * This software is provided "as is" without express or implied
     * warranty, and with no claim as to its suitability for any purpose.
     */
    #include <iostream>
    #include <string>
    #include <cstdlib>
    #include "stack1.hpp"
    
    int main()
    {
        try {
            Stack<int>         intStack;       // stack of ints
            Stack<std::string> stringStack;    // stack of strings
    
            // manipulate int stack
            intStack.push(7);
            std::cout << intStack.top() << std::endl;
    
            // manipulate string stack
            stringStack.push("hello");
            std::cout << stringStack.top() << std::endl; 
            stringStack.pop();
            stringStack.pop();
        }
        catch (std::exception const& ex) {
            std::cerr << "Exception: " << ex.what() << std::endl;
            return EXIT_FAILURE;  // exit program with ERROR status
        }
    }

    上例声明了 Stack<int>这样一个类型,表示在该 class template 中 T 被替换为 int。于是 intStack成为这样一个object:内部使用「可容纳int 数据做为元素」的vector,并将被调用之任何成员函数都「以int 类型进行实例化」。同样道理,Stack<std::string> 表示:stringStack 被创建为这样一个object:内部使用「可容纳string 数据做为元素」的vector,并将被调用之任何成员函数都「以std::string 类型进行实例化」。

    注意,惟有被调用到的成员函数,才会被实例化(instantiated)。对class templates 而言,只有当某个成员函数被使用时,才会进行实例化。无疑地这么做可以节省时间和空间。另一个好处是,你甚至可以实例化一个class template,而具现类型并不需要完整支持「class template 内与该 类型有关的所有操作」—前提是那些操作并未真正被叫用。举个例子,考虑某个class,其某些成员函数使用operator< 对内部元素排序;只要避免调用这些函数,就可以以一个「并不支持 operator<」的类型来实例化这个 class template。

    本例中的 default 构造函数、push()函数和 top()函数都同时被 int 和 string 类型加以实现(实例化)。然而 pop()只被 string 实例化(译注:因为 pop()只被 stringStack 调用)。如果 class template 拥有 static 成员,这些 static 成员会针对每一种被使用的类型完成实例化。

    你可以像面对任何基本类型一样地使用一个实例化后的class template类型,前提是你的使用方式合法:

    void foo (Stack<int> const& s) // 参数 s 是一个 int stack
    {
    Stack<int> istack[10]; // istack 是一个「含有 10 个 int stacks」的 array
    ...
    }

    运用 typedef,你可以更方便地使用 class templates:

    typedef Stack<int> IntStack;
    void foo (IntStack const& s) // 参数 s 是一个 int stack
    {
    IntStack istack[10]; // istack 是一个「含有 10 个 int stacks」的 array
    ...
    }

    注意,在C++中,typedef 并不产生新类型,只是为既有类型产生一个别名(type alias)。因此

    typedef Stack<int> IntStack;
    
    IntStack 和 Stack<int>拥有(代表)相同类型,可以互换使用,可以相互赋值(assigned)。
    
    Template arguments 可以是任意类型,例如可以是「指向 float」的指针,或甚至是个 int stack:
    
    Stack<float*> floatPtrStack; // stack of float
    
    pointers Stack<Stack<int> > intStackStack; // stack of stack of ints 惟一的条件是:该类型必须支持所有「实际被调用到的操作」。
    
    注意,你必须在相邻两个右角括号之间插入一些空白符号(像上面那样),否则就等于使用了
    
    operator>>,那会导致语法错误:
    
    Stack<Stack<int>> intStackStack; // 错误:此处不允许使用 >>
  • 相关阅读:
    HTML音频/视频DOM 参考手册
    HTML5 应用程序缓存 和 视频
    HTML 5 新的Input类型
    快速开发平台消息通知中心之短信发送
    设计模式之单例设计模式
    反射
    多线程之线程安全
    01-sql优化及索引
    多线程之线程状态
    shell/linux定时清理日志文件
  • 原文地址:https://www.cnblogs.com/jianfengyun/p/3719222.html
Copyright © 2020-2023  润新知