跳转到内容

更多 C++ 惯用法/泛型容器惯用法

来自 Wikibooks,开放世界中的开放书籍

泛型容器惯用法

[编辑 | 编辑源代码]

创建泛型容器类(向量、列表、堆栈),对它们的值类型施加最小的要求。这些要求只是复制构造函数和非抛出析构函数。

如果需要真正的泛型容器(如 STL),那么在 C++ 中开发泛型容器可能会变得很复杂。放宽对类型 T 的要求是开发真正泛型容器的关键。有几个 C++ 惯用法实际上可以实现对类型 T 的“最低公分母”要求。

让我们以堆栈为例。

template<class T>
class Stack
{
  int size_;
  T * array_;
  int top_;
 public:
  Stack (int size=10)
   : size_(size),
    array_ (new T [size]), // T must support default construction
    top_(0)
  { }
  void push (const T & value)
  {
   array_[top_++] = value; // T must support assignment operator.
  }
  T pop ()
  {
   return array_[--top_]; // T must support copy-construction. No destructor is called here
  }
  ~Stack () throw() { delete [] array_; } // T must support non-throwing destructor
};

除了某些数组边界问题外,上面的实现看起来非常明显。但它很天真。它对类型 T 的要求比实际需要的更多。上面的实现要求在类型 T 上定义以下操作

  • T 的默认构造函数
  • T 的复制构造函数
  • T 的非抛出析构函数
  • T 的复制赋值运算符

理想情况下,堆栈不应该在其中构造比对其执行的 push 操作次数更多的对象。类似地,在每次 pop 操作之后,应该从堆栈中弹出并销毁一个对象。上面的实现没有做到这一点。其中一个原因是它使用了类型 T 的默认构造函数,这完全没有必要。

实际上,使用构造销毁泛型容器惯用法,可以将对类型 T 的要求减少到以下内容。

  • 复制构造函数
  • 非抛出析构函数。

解决方案和示例代码

[编辑 | 编辑源代码]

为了实现这一点,泛型容器应该能够分配未初始化的内存,并且只在“初始化”每个元素时调用一次构造函数。这可以通过以下三个泛型容器惯用法来实现

#include <algorithm>
// construct helper using placement new:
template <class T1, class T2>
void construct (T1 &p, const T2 &value)
{
 new (&p) T1(value); // T must support copy-constructor
}

// destroy helper to invoke destructor explicitly.
template <class T>
void destroy (T const &t) throw ()
{
 t.~T(); // T must support non-throwing destructor
}

template<class T>
class Stack
{
  int size_;
  T * array_;
  int top_;
 public:
  Stack (int size=10)
   : size_(size),
    array_ (static_cast <T *>(::operator new (sizeof (T) * size))), // T need not support default construction
    top_(0)
  { }
  void push (const T & value)
  {
   construct (array_[top_++], value); // T need not support assignment operator.
  }
  T top ()
  {
    return array_[top_ - 1]; // T should support copy construction
  }
  void pop()
  {
   destroy (array_[--top_]);   // T destroyed
  }
  ~Stack () throw()
  {
   std::for_each(array_, array_ + top_, destroy<T>);
   ::operator delete(array_); // Global scope operator delete.
  }
};
class X
{
 public:
  X (int) {} // No default constructor for X.
 private:
  X & operator = (const X &); // assignment operator is private
};
int main (void)
{
 Stack <X> s; // X works with Stack!

 return 0;
}

operator new 分配未初始化的内存。它是一种调用 malloc 的花哨方法。construct 辅助模板函数调用就地 new,进而对初始化的内存调用复制构造函数。指针 p 应该是在使用 operator new 分配的未初始化内存块中。如果 end 是一个指向容器最后一个初始化元素之后的元素的迭代器,那么从 end 到 end_of_allocation 范围内的指针不应指向类型 T 的对象,而应指向未初始化的内存。当从容器中删除元素时,应该调用它们的析构函数。如所示,destroy 辅助函数在这里可能会有所帮助。类似地,要删除一个范围,另一个重载的 destroy 函数可以接受两个迭代器。它本质上对序列中的每个元素调用第一个 destroy 辅助函数。

已知用途

[编辑 | 编辑源代码]

所有 STL 容器都采用类似的技术。它们对模板参数类型有尽可能少的限制。另一方面,一些流行的 C++ 库对可参数化类型的要求比必要的高。

[编辑 | 编辑源代码]

还有其他几个泛型容器惯用法。

参考文献

[编辑 | 编辑源代码]

设计异常安全的泛型容器 -- Herb Sutter

华夏公益教科书