C++ Templates (2.1 類别範本Stack的實現 Implementation of Class Template Stack)

失落孤舟發表於2020-08-20

返回完整目錄

2.1 類别範本Stack的實現 Implementation of Class Template Stack

正如函式模板,可以如下方式在一個標頭檔案中宣告和定義類Stack<>

// basics/stack1.hpp

#include <vector>
#include <cassert>

template <typename T>
class Stack
{
private:
      std::vector<T> elems;      //元素

public:
      void push(T const& elem);      //壓入元素
      void pop();      // 彈出元素
      T const& top() const;      //返回頂上的元素
      bool empty()      //返回棧stack是否為空
      {
            return elems.empty();
      }
};

template <typename T>
void Stack<T>::push(T const& elem)
{
      elems.push_back(elem);      //將elem的拷貝放入elems末尾
}

template <typename T>
void Stack<T>::pop()
{
      assert(!elems.empty());
      elems.pop_back();      //移除最後一個元素
}

template <typename T>
T const& Stack<T>::top() const
{
      assert(!elems.empty());
      return elems.back();      //返回最後一個元素
}

正如所看到的,該類别範本使用C++標準庫的vector<>實現,這樣便無需實現記憶體管理、拷貝控制和賦值運算,這樣便可以將重心放在類别範本的介面上。

2.1.1 宣告類别範本 Declaration of Class Templates

宣告類别範本與宣告函式模板類似:在宣告之前,必須宣告一個或多個型別引數的識別符號。再一次,T是一個常用的識別符號:

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

此處的關鍵字typename也可以用class代替:

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

在類别範本中,T可以像其他任何型別一樣用於宣告成員和成員函式(member function)。該例子中,T用於宣告成員的型別為T的向量vector,用於宣告成員函式push()使用T型別作為引數,用於宣告成員函式top()的返回型別。

template <typename T>
class Stack
{
private:
      std::vector<T> elems;      //元素

public:
      void push(T const& elem);      //壓入元素
      void pop();      // 彈出元素
      T const& top() const;      //返回頂上的元素
      bool empty()      //返回棧stack是否為空
      {
            return elems.empty();
      }
};

該類的型別為Stack,其中T為模板引數。因此,只要在宣告中使用該類的型別,必須使用Stack,除非模板引數可以推斷而得。然而,在類别範本內,使用沒有模板實參的類名意味著將類别範本實參作為模板引數(However, inside a class template using the class name not followed by template arguments represents the class with its template parameters as its arguments.)(詳見13.2.3節)。

比如,如果必須宣告建構函式和賦值運算子,這通常看起來像這樣:

template <typename T>
class Stack
{
      ...
      Stack(Stack const&);      //拷貝構造
      Stack& operator=(Stack const&);      //賦值運算子
};

這與如下形式等價:

template <typename T>
class Stack
{
      ...
      Stack(Stack<T> const&);      //拷貝賦值
      Stack<T>& operator=(Stack<T> const&);      //賦值運算子
};

但通常意味著對特殊模板引數的特殊處理,因此第一種形式更好。

然而,在類結構之外需要指定模板引數:

template <typename T>
bool operator==(Stack<T> const& lhs, Stack<T> const& rhs);

注意到在需要類名而不是類的型別的地方,僅僅使用Stack便可以。這特別是在建構函式和解構函式名字的情形中。

與非模板類不同,不能在函式內部或者塊作用域(block scope)內宣告類别範本。通常,模板只能定義在全域性作用域(global scope)或者名稱空間作用域(namespace scope)或者類宣告內(詳見12.1節)。

2.1.2 成員函式實現 Implementation of Member Functions

定義類别範本的成員函式必須指定這是一個模板且必須使用類别範本的完整型別限制。因此,型別Stack的成員函式push()的實現為

template <typename T>
void Stack<T>::push(T const& elem)
{
      elems.push_back(elem);      //將elem的拷貝放入elems末尾
}

該情況下,成員向量的push_bask()方法被呼叫,將元素放入向量vector的末尾。

注意到向量vector的pop_back()將移除最後一個元素但不返回,這行為的原因是異常安全(exception safety)。不可能實現一個返回移除元素的完全異常安全的pop()版本(該問題首先由Tom Cargill在[CargilExceptionSafety]中的第10項條款[SutterExceptional]中討論)。然而,如果忽略該危險,可以實現返回移除的元素的pop()。為實現此功能,簡單地使用T來宣告型別為元素型別的區域性變數:

template <typename T>
T Stack<T>::pop()
{
      assert(!elems.empty());
      T elem = elems.back();      //儲存最後一個元素
      elems.pop_back();      //移除最後一個元素
      return elem;      //返回儲存元素的拷貝
}

由於當vector中沒有元素時,back()(返回最後一個元素)和pop_back()(移除最後一個元素)都將有未定義的行為,因此需要檢查棧是否為空。如果為空,則斷言(assert),因為在空的棧上呼叫pop()是錯誤的。在top()上也需要這麼做,它返回頂上的元素但不移除:

template <typename T>
T const& Stack<T>::top() const
{
      assert(elems.empty());
      return elems.back();      //返回最後一個元素
}

當然,對於任何成員函式,也可以將類别範本的成員函式在類的宣告中實現為inline,比如:

template <typename T>
class Stack
{
      ...
      void push(T const& elem)
      {
            elems.push_back(elem);      //將elem放入末尾
      }
};

相關文章