练习16.1

给出实例化的定义。

解:

当编译器实例化一个模版时,它使用实际的模版参数代替对应的模版参数来创建出模版的一个新“实例”。

练习16.2

编写并测试你自己版本的 compare 函数。

解:

  1. template<typename T>
  2. int compare(const T& lhs, const T& rhs)
  3. {
  4. if (lhs < rhs) return -1;
  5. if (rhs < lhs) return 1;
  6. return 0;
  7. }

练习16.3

对两个 Sales_data 对象调用你的 compare 函数,观察编译器在实例化过程中如何处理错误。

解:

error: no match for 'operator<'

练习16.4

编写行为类似标准库 find 算法的模版。函数需要两个模版类型参数,一个表示函数的迭代器参数,另一个表示值的类型。使用你的函数在一个 vector<int> 和一个list<string>中查找给定值。

解:

  1. template<typename Iterator, typename Value>
  2. Iterator find(Iterator first, Iterator last, const Value& v)
  3. {
  4. for ( ; first != last && *first != value; ++first);
  5. return first;
  6. }

练习16.5

为6.2.4节中的print函数编写模版版本,它接受一个数组的引用,能处理任意大小、任意元素类型的数组。

解:

  1. template<typename Array>
  2. void print(const Array& arr)
  3. {
  4. for (const auto& elem : arr)
  5. std::cout << elem << std::endl;
  6. }

练习16.6

你认为接受一个数组实参的标准库函数 beginend 是如何工作的?定义你自己版本的 beginend

解:

  1. template<typename T, unsigned N>
  2. T* begin(const T (&arr)[N])
  3. {
  4. return arr;
  5. }
  6. template<typename T, unsigned N>
  7. T* end(const T (&arr)[N])
  8. {
  9. return arr + N;
  10. }

练习16.7

编写一个 constexpr 模版,返回给定数组的大小。

解:

  1. template<typename T, typename N> constexpr
  2. unsigned size(const T (&arr)[N])
  3. {
  4. return N;
  5. }

练习16.8

在第97页的“关键概念”中,我们注意到,C++程序员喜欢使用 != 而不喜欢 < 。解释这个习惯的原因。

解:

因为大多数类只定义了 != 操作而没有定义 < 操作,使用 != 可以降低对要处理的类型的要求。

练习16.9

什么是函数模版,什么是类模版?

解:

一个函数模版就是一个公式,可用来生成针对特定类型的函数版本。类模版是用来生成类的蓝图的,与函数模版的不同之处是,编译器不能为类模版推断模版参数类型。如果我们已经多次看到,为了使用类模版,我们必须在模版名后的尖括号中提供额外信息。

练习16.10

当一个类模版被实例化时,会发生什么?

解:

一个类模版的每个实例都形成一个独立的类。

练习16.11

下面 List 的定义是错误的。应如何修改它?

  1. template <typename elemType> class ListItem;
  2. template <typename elemType> class List {
  3. public:
  4. List<elemType>();
  5. List<elemType>(const List<elemType> &);
  6. List<elemType>& operator=(const List<elemType> &);
  7. ~List();
  8. void insert(ListItem *ptr, elemType value);
  9. private:
  10. ListItem *front, *end;
  11. };

解:

模版需要模版参数,应该修改为如下:

  1. template <typename elemType> class ListItem;
  2. template <typename elemType> class List{
  3. public:
  4. List<elemType>();
  5. List<elemType>(const List<elemType> &);
  6. List<elemType>& operator=(const List<elemType> &);
  7. ~List();
  8. void insert(ListItem<elemType> *ptr, elemType value);
  9. private:
  10. ListItem<elemType> *front, *end;
  11. };

练习16.12

编写你自己版本的 BlobBlobPtr 模版,包含书中未定义的多个const成员。

解:

Blob:

  1. #include <memory>
  2. #include <vector>
  3. template<typename T> class Blob
  4. {
  5. public:
  6. typedef T value_type;
  7. typedef typename std::vector<T>::size_type size_type;
  8. // constructors
  9. Blob();
  10. Blob(std::initializer_list<T> il);
  11. // number of elements in the Blob
  12. size_type size() const { return data->size(); }
  13. bool empty() const { return data->empty(); }
  14. void push_back(const T& t) { data->push_back(t); }
  15. void push_back(T&& t) { data->push_back(std::move(t)); }
  16. void pop_back();
  17. // element access
  18. T& back();
  19. T& operator[](size_type i);
  20. const T& back() const;
  21. const T& operator [](size_type i) const;
  22. private:
  23. std::shared_ptr<std::vector<T>> data;
  24. // throw msg if data[i] isn't valid
  25. void check(size_type i, const std::string &msg) const;
  26. };
  27. // constructors
  28. template<typename T>
  29. Blob<T>::Blob() : data(std::make_shared<std::vector<T>>())
  30. {}
  31. template<typename T>
  32. Blob<T>::Blob(std::initializer_list<T> il) :
  33. data(std::make_shared<std::vector<T>>(il))
  34. {}
  35. template<typename T>
  36. void Blob<T>::check(size_type i, const std::string &msg) const
  37. {
  38. if (i >= data->size())
  39. throw std::out_of_range(msg);
  40. }
  41. template<typename T>
  42. T& Blob<T>::back()
  43. {
  44. check(0, "back on empty Blob");
  45. return data->back();
  46. }
  47. template<typename T>
  48. const T& Blob<T>::back() const
  49. {
  50. check(0, "back on empty Blob");
  51. return data->back();
  52. }
  53. template<typename T>
  54. T& Blob<T>::operator [](size_type i)
  55. {
  56. // if i is too big, check function will throw, preventing access to a nonexistent element
  57. check(i, "subscript out of range");
  58. return (*data)[i];
  59. }
  60. template<typename T>
  61. const T& Blob<T>::operator [](size_type i) const
  62. {
  63. // if i is too big, check function will throw, preventing access to a nonexistent element
  64. check(i, "subscript out of range");
  65. return (*data)[i];
  66. }
  67. template<typename T>
  68. void Blob<T>::pop_back()
  69. {
  70. check(0, "pop_back on empty Blob");
  71. data->pop_back();
  72. }

BlobPtr:

  1. #include "Blob.h"
  2. #include <memory>
  3. #include <vector>
  4. template <typename> class BlobPtr;
  5. template <typename T>
  6. bool operator ==(const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
  7. template <typename T>
  8. bool operator < (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
  9. template<typename T> class BlobPtr
  10. {
  11. friend bool operator ==<T>
  12. (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
  13. friend bool operator < <T>
  14. (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
  15. public:
  16. BlobPtr() : curr(0) {}
  17. BlobPtr(Blob<T>& a, std::size_t sz = 0) :
  18. wptr(a.data), curr(sz)
  19. {}
  20. T& operator*() const
  21. {
  22. auto p = check(curr, "dereference past end");
  23. return (*p)[curr];
  24. }
  25. // prefix
  26. BlobPtr& operator++();
  27. BlobPtr& operator--();
  28. // postfix
  29. BlobPtr operator ++(int);
  30. BlobPtr operator --(int);
  31. private:
  32. // returns a shared_ptr to the vector if the check succeeds
  33. std::shared_ptr<std::vector<T>>
  34. check(std::size_t, const std::string&) const;
  35. std::weak_ptr<std::vector<T>> wptr;
  36. std::size_t curr;
  37. };
  38. // prefix ++
  39. template<typename T>
  40. BlobPtr<T>& BlobPtr<T>::operator ++()
  41. {
  42. // if curr already points past the end of the container, can't increment it
  43. check(curr, "increment past end of StrBlob");
  44. ++curr;
  45. return *this;
  46. }
  47. // prefix --
  48. template<typename T>
  49. BlobPtr<T>& BlobPtr<T>::operator --()
  50. {
  51. --curr;
  52. check(curr, "decrement past begin of BlobPtr");
  53. return *this;
  54. }
  55. // postfix ++
  56. template<typename T>
  57. BlobPtr<T> BlobPtr<T>::operator ++(int)
  58. {
  59. BlobPtr ret = *this;
  60. ++*this;
  61. return ret;
  62. }
  63. // postfix --
  64. template<typename T>
  65. BlobPtr<T> BlobPtr<T>::operator --(int)
  66. {
  67. BlobPtr ret = *this;
  68. --*this;
  69. return ret;
  70. }
  71. template<typename T> bool operator==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs)
  72. {
  73. if (lhs.wptr.lock() != rhs.wptr.lock())
  74. {
  75. throw runtime_error("ptrs to different Blobs!");
  76. }
  77. return lhs.i == rhs.i;
  78. }
  79. template<typename T> bool operator< (const BlobPtr<T> &lhs, const BlobPtr<T> &rhs)
  80. {
  81. if (lhs.wptr.lock() != rhs.wptr.lock())
  82. {
  83. throw runtime_error("ptrs to different Blobs!");
  84. }
  85. return lhs.i < rhs.i;
  86. }

练习16.13

解释你为 BlobPtr 的相等和关系运算符选择哪种类型的友好关系?

解:

这里需要与类型一一对应,所以就选择一对一友好关系。

练习16.14

编写 Screen 类模版,用非类型参数定义 Screen 的高和宽。

解:

Screen

  1. #include <string>
  2. #include <iostream>
  3. template<unsigned H, unsigned W>
  4. class Screen
  5. {
  6. public:
  7. typedef std::string::size_type pos;
  8. Screen() = default; // needed because Screen has another constructor
  9. // cursor initialized to 0 by its in-class initializer
  10. Screen(char c) :contents(H * W, c) {}
  11. char get() const // get the character at the cursor
  12. {
  13. return contents[cursor];
  14. } // implicitly inline
  15. Screen &move(pos r, pos c); // can be made inline later
  16. friend std::ostream & operator<< (std::ostream &os, const Screen<H, W> & c)
  17. {
  18. unsigned int i, j;
  19. for (i = 0; i<c.height; i++)
  20. {
  21. os << c.contents.substr(0, W) << std::endl;
  22. }
  23. return os;
  24. }
  25. friend std::istream & operator>> (std::istream &is, Screen & c)
  26. {
  27. char a;
  28. is >> a;
  29. std::string temp(H*W, a);
  30. c.contents = temp;
  31. return is;
  32. }
  33. private:
  34. pos cursor = 0;
  35. pos height = H, width = W;
  36. std::string contents;
  37. };
  38. template<unsigned H, unsigned W>
  39. inline Screen<H, W>& Screen<H, W>::move(pos r, pos c)
  40. {
  41. pos row = r * width;
  42. cursor = row + c;
  43. return *this;
  44. }

练习16.15

为你的 Screen 模版实现输入和输出运算符。Screen 类需要哪些友元(如果需要的话)来令输入和输出运算符正确工作?解释每个友元声明(如果有的话)为什么是必要的。

解:

类的 operator<<operator>> 应该是类的友元。

练习16.16

StrVec 类重写为模版,命名为 Vec

解:

Vec:

  1. #include <memory>
  2. /**
  3. * @brief a vector like class
  4. */
  5. template<typename T>
  6. class Vec
  7. {
  8. public:
  9. Vec() :element(nullptr), first_free(nullptr), cap(nullptr) {}
  10. Vec(std::initializer_list<T> l);
  11. Vec(const Vec& v);
  12. Vec& operator =(const Vec& rhs);
  13. ~Vec();
  14. // memmbers
  15. void push_back(const T& t);
  16. std::size_t size() const { return first_free - element; }
  17. std::size_t capacity()const { return cap - element; }
  18. T* begin() const { return element; }
  19. T* end() const { return first_free; }
  20. void reserve(std::size_t n);
  21. void resize(std::size_t n);
  22. void resize(std::size_t n, const T& t);
  23. private:
  24. // data members
  25. T* element;
  26. T* first_free;
  27. T* cap;
  28. std::allocator<T> alloc;
  29. // utillities
  30. void reallocate();
  31. void chk_n_alloc() { if (size() == capacity()) reallocate(); }
  32. void free();
  33. void wy_alloc_n_move(std::size_t n);
  34. std::pair<T*, T*> alloc_n_copy(T* b, T* e);
  35. };
  36. // copy constructor
  37. template<typename T>
  38. Vec<T>::Vec(const Vec &v)
  39. {
  40. /**
  41. * @brief newData is a pair of pointers pointing to newly allocated and copied
  42. * from range : [b, e)
  43. */
  44. std::pair<T*, T*> newData = alloc_n_copy(v.begin(), v.end());
  45. element = newData.first;
  46. first_free = cap = newData.second;
  47. }
  48. // constructor that takes initializer_list<T>
  49. template<typename T>
  50. Vec<T>::Vec(std::initializer_list<T> l)
  51. {
  52. // allocate memory as large as l.size()
  53. T* const newData = alloc.allocate(l.size());
  54. // copy elements from l to the address allocated
  55. T* p = newData;
  56. for (const auto &t : l)
  57. alloc.construct(p++, t);
  58. // build data structure
  59. element = newData;
  60. first_free = cap = element + l.size();
  61. }
  62. // operator =
  63. template<typename T>
  64. Vec<T>& Vec<T>::operator =(const Vec& rhs)
  65. {
  66. // allocate and copy first to protect against self_assignment
  67. std::pair<T*, T*> newData = alloc_n_copy(rhs.begin(), rhs.end());
  68. // destroy and deallocate
  69. free();
  70. // update data structure
  71. element = newData.first;
  72. first_free = cap = newData.second;
  73. return *this;
  74. }
  75. // destructor
  76. template<typename T>
  77. Vec<T>::~Vec()
  78. {
  79. free();
  80. }
  81. /**
  82. * @brief allocate new memeory if nessary and push back the new T
  83. * @param t new T
  84. */
  85. template<typename T>
  86. void Vec<T>::push_back(const T &t)
  87. {
  88. chk_n_alloc();
  89. alloc.construct(first_free++, t);
  90. }
  91. /**
  92. * @brief preallocate enough memory for specified number of elements
  93. * @param n number of elements required
  94. */
  95. template<typename T>
  96. void Vec<T>::reserve(std::size_t n)
  97. {
  98. // if n too small, just return without doing anything
  99. if (n <= capacity()) return;
  100. // allocate new memory and move data from old address to the new one
  101. wy_alloc_n_move(n);
  102. }
  103. /**
  104. * @brief Resizes to the specified number of elements.
  105. * @param n Number of elements the %vector should contain.
  106. *
  107. * This function will resize it to the specified
  108. * number of elements. If the number is smaller than the
  109. * current size it is truncated, otherwise
  110. * default constructed elements are appended.
  111. */
  112. template<typename T>
  113. void Vec<T>::resize(std::size_t n)
  114. {
  115. resize(n, T());
  116. }
  117. /**
  118. * @brief Resizes it to the specified number of elements.
  119. * @param __new_size Number of elements it should contain.
  120. * @param __x Data with which new elements should be populated.
  121. *
  122. * This function will resize it to the specified
  123. * number of elements. If the number is smaller than the
  124. * current size the it is truncated, otherwise
  125. * the it is extended and new elements are populated with
  126. * given data.
  127. */
  128. template<typename T>
  129. void Vec<T>::resize(std::size_t n, const T &t)
  130. {
  131. if (n < size())
  132. {
  133. // destroy the range [element+n, first_free) using destructor
  134. for (auto p = element + n; p != first_free;)
  135. alloc.destroy(p++);
  136. // update first_free to point to the new address
  137. first_free = element + n;
  138. }
  139. else if (n > size())
  140. {
  141. for (auto i = size(); i != n; ++i)
  142. push_back(t);
  143. }
  144. }
  145. /**
  146. * @brief allocate new space for the given range and copy them into it
  147. * @param b
  148. * @param e
  149. * @return a pair of pointers pointing to [first element , one past the last) in the new space
  150. */
  151. template<typename T>
  152. std::pair<T*, T*>
  153. Vec<T>::alloc_n_copy(T *b, T *e)
  154. {
  155. // calculate the size needed and allocate space accordingly
  156. T* data = alloc.allocate(e - b);
  157. return{ data, std::uninitialized_copy(b, e, data) };
  158. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  159. // which copies the range[first, last) to the space to which
  160. // the starting address data is pointing.
  161. // This function returns a pointer to one past the last element
  162. }
  163. /**
  164. * @brief destroy the elements and deallocate the space previously allocated.
  165. */
  166. template<typename T>
  167. void Vec<T>::free()
  168. {
  169. // if not nullptr
  170. if (element)
  171. {
  172. // destroy it in reverse order.
  173. for (auto p = first_free; p != element;)
  174. alloc.destroy(--p);
  175. alloc.deallocate(element, capacity());
  176. }
  177. }
  178. /**
  179. * @brief allocate memory for spicified number of elements
  180. * @param n
  181. * @note it's user's responsibility to ensure that @param n is greater than
  182. * the current capacity.
  183. */
  184. template<typename T>
  185. void Vec<T>::wy_alloc_n_move(std::size_t n)
  186. {
  187. // allocate as required.
  188. std::size_t newCapacity = n;
  189. T* newData = alloc.allocate(newCapacity);
  190. // move the data from old place to the new one
  191. T* dest = newData;
  192. T* old = element;
  193. for (std::size_t i = 0; i != size(); ++i)
  194. alloc.construct(dest++, std::move(*old++));
  195. free();
  196. // update data structure
  197. element = newData;
  198. first_free = dest;
  199. cap = element + newCapacity;
  200. }
  201. /**
  202. * @brief Double the capacity and using std::move move the original data to the newly
  203. * allocated memory
  204. */
  205. template<typename T>
  206. void Vec<T>::reallocate()
  207. {
  208. // calculate the new capacity required
  209. std::size_t newCapacity = size() ? 2 * size() : 1;
  210. // allocate and move old data to the new space
  211. wy_alloc_n_move(newCapacity);
  212. }

练习16.17

声明为 typename 的类型参数和声明为 class 的类型参数有什么不同(如果有的话)?什么时候必须使用typename

解:

没有什么不同。当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename,而不能使用 class

练习16.18

解释下面每个函数模版声明并指出它们是否非法。更正你发现的每个错误。

  1. (a) template <typename T, U, typename V> void f1(T, U, V);
  2. (b) template <typename T> T f2(int &T);
  3. (c) inline template <typename T> T foo(T, unsigned int *);
  4. (d) template <typename T> f4(T, T);
  5. (e) typedef char Ctype;
  6. template <typename Ctype> Ctype f5(Ctype a);

解:

  • (a) 非法。应该为 template <typename T, typename U, typename V> void f1(T, U, V);
  • (b) 非法。应该为 template <typename T> T f2(int &t);
  • (c) 非法。应该为 template <typename T> inline T foo(T, unsigned int*);
  • (d) 非法。应该为 template <typename T> T f4(T, T);
  • (e) 非法。Ctype 被隐藏了。

练习16.19

编写函数,接受一个容器的引用,打印容器中的元素。使用容器的 size_typesize成员来控制打印元素的循环。

解:

  1. template<typename Container>
  2. void print(const Container& c)
  3. {
  4. for (typename Container::size_type i = 0; i != c.size(); ++i)
  5. std::cout << c[i] << " ";
  6. }

练习16.20

重写上一题的函数,使用beginend 返回的迭代器来控制循环。

解:

  1. template<typename Container>
  2. void print(const Container& c)
  3. {
  4. for (auto it = c.begin(); it != c.end(); ++it)
  5. std::cout << *it << " ";
  6. }

练习16.21

编写你自己的 DebugDelete 版本。

解:

DebugDelete

  1. #include <iostream>
  2. class DebugDelete
  3. {
  4. public:
  5. DebugDelete(std::ostream& s = std::cerr) : os(s) {}
  6. template<typename T>
  7. void operator() (T* p) const
  8. {
  9. os << "deleting unique_ptr" << std::endl;
  10. delete p;
  11. }
  12. private:
  13. std::ostream& os;
  14. };

练习16.22

修改12.3节中你的 TextQuery 程序,令 shared_ptr 成员使用 DebugDelete 作为它们的删除器。

解:

练习16.23

预测在你的查询主程序中何时会执行调用运算符。如果你的预测和实际不符,确认你理解了原因。

解:

练习16.24

为你的 Blob 模版添加一个构造函数,它接受两个迭代器。

解:

  1. template<typename T> //for class
  2. template<typename It> //for this member
  3. Blob<T>::Blob(It b, It e) :
  4. data(std::make_shared<std::vector<T>>(b, e))
  5. { }

练习16.25

解释下面这些声明的含义。

  1. extern template class vector<string>;
  2. template class vector<Sales_data>;

解:

前者是模版声明,后者是实例化定义。

练习16.26

假设 NoDefault 是一个没有默认构造函数的类,我们可以显式实例化 vector<NoDefualt>吗?如果不可以,解释为什么。

解:

不可以。如

  1. std::vector<NoDefault> vec(10);

会使用 NoDefault 的默认构造函数,而 NoDefault 没有默认构造函数,因此是不可以的。

练习16.27

对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模版被实例化,解释为什么;如果未实例化,解释为什么没有。

  1. template <typename T> class Stack { };
  2. void f1(Stack<char>); //(a)
  3. class Exercise {
  4. Stack<double> &rds; //(b)
  5. Stack<int> si; //(c)
  6. };
  7. int main() {
  8. Stack<char> *sc; //(d)
  9. f1(*sc); //(e)
  10. int iObj = sizeof(Stack<string>); //(f)
  11. }

解:

(a)、(b)、(c)、(f) 都发生了实例化,(d)、(e) 没有实例化。

练习16.28

编写你自己版本的 shared_ptrunique_ptr

解:

shared_ptr

  1. #pragma once
  2. #include <functional>
  3. #include "delete.h"
  4. namespace cp5
  5. {
  6. template<typename T>
  7. class SharedPointer;
  8. template<typename T>
  9. auto swap(SharedPointer<T>& lhs, SharedPointer<T>& rhs)
  10. {
  11. using std::swap;
  12. swap(lhs.ptr, rhs.ptr);
  13. swap(lhs.ref_count, rhs.ref_count);
  14. swap(lhs.deleter, rhs.deleter);
  15. }
  16. template<typename T>
  17. class SharedPointer
  18. {
  19. public:
  20. //
  21. // Default Ctor
  22. //
  23. SharedPointer()
  24. : ptr{ nullptr }, ref_count{ new std::size_t(1) }, deleter{ cp5::Delete{} }
  25. {}
  26. //
  27. // Ctor that takes raw pointer
  28. //
  29. explicit SharedPointer(T* raw_ptr)
  30. : ptr{ raw_ptr }, ref_count{ new std::size_t(1) }, deleter{ cp5::Delete{} }
  31. {}
  32. //
  33. // Copy Ctor
  34. //
  35. SharedPointer(SharedPointer const& other)
  36. : ptr{ other.ptr }, ref_count{ other.ref_count }, deleter{ other.deleter }
  37. {
  38. ++*ref_count;
  39. }
  40. //
  41. // Move Ctor
  42. //
  43. SharedPointer(SharedPointer && other) noexcept
  44. : ptr{ other.ptr }, ref_count{ other.ref_count }, deleter{ std::move(other.deleter) }
  45. {
  46. other.ptr = nullptr;
  47. other.ref_count = nullptr;
  48. }
  49. //
  50. // Copy assignment
  51. //
  52. SharedPointer& operator=(SharedPointer const& rhs)
  53. {
  54. //increment first to ensure safty for self-assignment
  55. ++*rhs.ref_count;
  56. decrement_and_destroy();
  57. ptr = rhs.ptr, ref_count = rhs.ref_count, deleter = rhs.deleter;
  58. return *this;
  59. }
  60. //
  61. // Move assignment
  62. //
  63. SharedPointer& operator=(SharedPointer && rhs) noexcept
  64. {
  65. cp5::swap(*this, rhs);
  66. rhs.decrement_and_destroy();
  67. return *this;
  68. }
  69. //
  70. // Conversion operator
  71. //
  72. operator bool() const
  73. {
  74. return ptr ? true : false;
  75. }
  76. //
  77. // Dereference
  78. //
  79. T& operator* () const
  80. {
  81. return *ptr;
  82. }
  83. //
  84. // Arrow
  85. //
  86. T* operator->() const
  87. {
  88. return &*ptr;
  89. }
  90. //
  91. // Use count
  92. //
  93. auto use_count() const
  94. {
  95. return *ref_count;
  96. }
  97. //
  98. // Get underlying pointer
  99. //
  100. auto get() const
  101. {
  102. return ptr;
  103. }
  104. //
  105. // Check if the unique user
  106. //
  107. auto unique() const
  108. {
  109. return 1 == *refCount;
  110. }
  111. //
  112. // Swap
  113. //
  114. auto swap(SharedPointer& rhs)
  115. {
  116. ::swap(*this, rhs);
  117. }
  118. //
  119. // Free the object pointed to, if unique
  120. //
  121. auto reset()
  122. {
  123. decrement_and_destroy();
  124. }
  125. //
  126. // Reset with the new raw pointer
  127. //
  128. auto reset(T* pointer)
  129. {
  130. if (ptr != pointer)
  131. {
  132. decrement_n_destroy();
  133. ptr = pointer;
  134. ref_count = new std::size_t(1);
  135. }
  136. }
  137. //
  138. // Reset with raw pointer and deleter
  139. //
  140. auto reset(T *pointer, const std::function<void(T*)>& d)
  141. {
  142. reset(pointer);
  143. deleter = d;
  144. }
  145. //
  146. // Dtor
  147. //
  148. ~SharedPointer()
  149. {
  150. decrement_and_destroy();
  151. }
  152. private:
  153. T* ptr;
  154. std::size_t* ref_count;
  155. std::function<void(T*)> deleter;
  156. auto decrement_and_destroy()
  157. {
  158. if (ptr && 0 == --*ref_count)
  159. delete ref_count,
  160. deleter(ptr);
  161. else if (!ptr)
  162. delete ref_count;
  163. ref_count = nullptr;
  164. ptr = nullptr;
  165. }
  166. };
  167. }//namespace

unique_ptr:

  1. #include "debugDelete.h"
  2. // forward declarations for friendship
  3. template<typename, typename> class unique_pointer;
  4. template<typename T, typename D> void
  5. swap(unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs);
  6. /**
  7. * @brief std::unique_ptr like class template.
  8. */
  9. template <typename T, typename D = DebugDelete>
  10. class unique_pointer
  11. {
  12. friend void swap<T, D>(unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs);
  13. public:
  14. // preventing copy and assignment
  15. unique_pointer(const unique_pointer&) = delete;
  16. unique_pointer& operator = (const unique_pointer&) = delete;
  17. // default constructor and one taking T*
  18. unique_pointer() = default;
  19. explicit unique_pointer(T* up) : ptr(up) {}
  20. // move constructor
  21. unique_pointer(unique_pointer&& up) noexcept
  22. : ptr(up.ptr) { up.ptr = nullptr; }
  23. // move assignment
  24. unique_pointer& operator =(unique_pointer&& rhs) noexcept;
  25. // std::nullptr_t assignment
  26. unique_pointer& operator =(std::nullptr_t n) noexcept;
  27. // operator overloaded : * -> bool
  28. T& operator *() const { return *ptr; }
  29. T* operator ->() const { return &this->operator *(); }
  30. operator bool() const { return ptr ? true : false; }
  31. // return the underlying pointer
  32. T* get() const noexcept{ return ptr; }
  33. // swap member using swap friend
  34. void swap(unique_pointer<T, D> &rhs) { ::swap(*this, rhs); }
  35. // free and make it point to nullptr or to p's pointee.
  36. void reset() noexcept{ deleter(ptr); ptr = nullptr; }
  37. void reset(T* p) noexcept{ deleter(ptr); ptr = p; }
  38. // return ptr and make ptr point to nullptr.
  39. T* release();
  40. ~unique_pointer()
  41. {
  42. deleter(ptr);
  43. }
  44. private:
  45. T* ptr = nullptr;
  46. D deleter = D();
  47. };
  48. // swap
  49. template<typename T, typename D>
  50. inline void
  51. swap(unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs)
  52. {
  53. using std::swap;
  54. swap(lhs.ptr, rhs.ptr);
  55. swap(lhs.deleter, rhs.deleter);
  56. }
  57. // move assignment
  58. template<typename T, typename D>
  59. inline unique_pointer<T, D>&
  60. unique_pointer<T, D>::operator =(unique_pointer&& rhs) noexcept
  61. {
  62. // prevent self-assignment
  63. if (this->ptr != rhs.ptr)
  64. {
  65. deleter(ptr);
  66. ptr = nullptr;
  67. ::swap(*this, rhs);
  68. }
  69. return *this;
  70. }
  71. // std::nullptr_t assignment
  72. template<typename T, typename D>
  73. inline unique_pointer<T, D>&
  74. unique_pointer<T, D>::operator =(std::nullptr_t n) noexcept
  75. {
  76. if (n == nullptr)
  77. {
  78. deleter(ptr); ptr = nullptr;
  79. }
  80. return *this;
  81. }
  82. // relinquish contrul by returnning ptr and making ptr point to nullptr.
  83. template<typename T, typename D>
  84. inline T*
  85. unique_pointer<T, D>::release()
  86. {
  87. T* ret = ptr;
  88. ptr = nullptr;
  89. return ret;
  90. }

练习16.29

修改你的 Blob 类,用你自己的 shared_ptr 代替标准库中的版本。

解:

练习16.30

重新运行你的一些程序,验证你的 shared_ptr 类和修改后的 Blob 类。(注意:实现 weak_ptr 类型超出了本书范围,因此你不能将BlobPtr类与你修改后的Blob一起使用。)

解:

练习16.31

如果我们将 DebugDeleteunique_ptr 一起使用,解释编译器将删除器处理为内联形式的可能方式。

解:

练习16.32

在模版实参推断过程中发生了什么?

解:

在模版实参推断过程中,编译器使用函数调用中的实参类型来寻找模版实参,用这些模版实参生成的函数版本与给定的函数调用最为匹配。

练习16.33

指出在模版实参推断过程中允许对函数实参进行的两种类型转换。

解:

  • const 转换:可以将一个非 const 对象的引用(或指针)传递给一个 const 的引用(或指针)形参。
  • 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。

练习16.34

对下面的代码解释每个调用是否合法。如果合法,T 的类型是什么?如果不合法,为什么?

  1. template <class T> int compare(const T&, const T&);
  2. (a) compare("hi", "world");
  3. (b) compare("bye", "dad");

解:

  • (a) 不合法。compare(const char [3], const char [6]), 两个实参类型不一致。
  • (b) 合法。compare(const char [4], const char [4]).

练习16.35

下面调用中哪些是错误的(如果有的话)?如果调用合法,T 的类型是什么?如果调用不合法,问题何在?

  1. template <typename T> T calc(T, int);
  2. tempalte <typename T> T fcn(T, T);
  3. double d; float f; char c;
  4. (a) calc(c, 'c');
  5. (b) calc(d, f);
  6. (c) fcn(c, 'c');
  7. (d) fcn(d, f);

解:

  • (a) 合法,类型为char
  • (b) 合法,类型为double
  • (c) 合法,类型为char
  • (d) 不合法,这里无法确定T的类型是float还是double

练习16.36

进行下面的调用会发生什么:

  1. template <typename T> f1(T, T);
  2. template <typename T1, typename T2) f2(T1, T2);
  3. int i = 0, j = 42, *p1 = &i, *p2 = &j;
  4. const int *cp1 = &i, *cp2 = &j;
  5. (a) f1(p1, p2);
  6. (b) f2(p1, p2);
  7. (c) f1(cp1, cp2);
  8. (d) f2(cp1, cp2);
  9. (e) f1(p1, cp1);
  10. (f) f2(p1, cp1);

解:

  1. (a) f1(int*, int*);
  2. (b) f2(int*, int*);
  3. (c) f1(const int*, const int*);
  4. (d) f2(const int*, const int*);
  5. (e) f1(int*, const int*); 这个使用就不合法
  6. (f) f2(int*, const int*);

练习16.37

标准库 max 函数有两个参数,它返回实参中的较大者。此函数有一个模版类型参数。你能在调用 max 时传递给它一个 int 和一个 double 吗?如果可以,如何做?如果不可以,为什么?

解:

可以。提供显式的模版实参:

  1. int a = 1;
  2. double b = 2;
  3. std::max<double>(a, b);

练习16.38

当我们调用 make_shared 时,必须提供一个显示模版实参。解释为什么需要显式模版实参以及它是如果使用的。

解:

如果不显示提供模版实参,那么 make_shared 无法推断要分配多大内存空间。

练习16.39

对16.1.1节 中的原始版本的 compare 函数,使用一个显式模版实参,使得可以向函数传递两个字符串字面量。

解:

  1. compare<std::string>("hello", "world")

练习16.40

下面的函数是否合法?如果不合法,为什么?如果合法,对可以传递的实参类型有什么限制(如果有的话)?返回类型是什么?

  1. template <typename It>
  2. auto fcn3(It beg, It end) -> decltype(*beg + 0)
  3. {
  4. //处理序列
  5. return *beg;
  6. }

解:

合法。该类型需要支持 + 操作。

练习16.41

编写一个新的 sum 版本,它返回类型保证足够大,足以容纳加法结果。

解:

  1. template<typename T>
  2. auto sum(T lhs, T rhs) -> decltype( lhs + rhs)
  3. {
  4. return lhs + rhs;
  5. }

练习16.42

对下面每个调用,确定 Tval 的类型:

  1. template <typename T> void g(T&& val);
  2. int i = 0; const int ci = i;
  3. (a) g(i);
  4. (b) g(ci);
  5. (c) g(i * ci);

解:

  1. (a) int&
  2. (b) const int&
  3. (c) int&&

练习16.43

使用上一题定义的函数,如果我们调用g(i = ci),g 的模版参数将是什么?

解:

i = ci 返回的是左值,因此 g 的模版参数是 int&

练习16.44

使用与第一题中相同的三个调用,如果 g 的函数参数声明为 T(而不是T&&),确定T的类型。如果g的函数参数是 const T&呢?

解:

当声明为T的时候,T的类型为int&
当声明为const T&的时候,T的类型为int&

练习16.45

如果下面的模版,如果我们对一个像42这样的字面常量调用g,解释会发生什么?如果我们对一个int 类型的变量调用g 呢?

  1. template <typename T> void g(T&& val) { vector<T> v; }

解:

当使用字面常量,T将为int
当使用int变量,T将为int&。编译的时候将会报错,因为没有办法对这种类型进行内存分配,无法创建vector<int&>

练习16.46

解释下面的循环,它来自13.5节中的 StrVec::reallocate:

  1. for (size_t i = 0; i != size(); ++i)
  2. alloc.construct(dest++, std::move(*elem++));

解:

在每个循环中,对 elem 的解引用操作 * 当中,会返回一个左值,std::move 函数将该左值转换为右值,提供给 construct 函数。

练习16.47

编写你自己版本的翻转函数,通过调用接受左值和右值引用参数的函数来测试它。

解:

  1. template<typename F, typename T1, typename T2>
  2. void flip(F f, T1&& t1, T2&& t2)
  3. {
  4. f(std::forward<T2>(t2), std::forward<T1>(t1));
  5. }

练习16.48

编写你自己版本的 debug_rep 函数。

解:

  1. template<typename T> std::string debug_rep(const T& t)
  2. {
  3. std::ostringstream ret;
  4. ret << t;
  5. return ret.str();
  6. }
  7. template<typename T> std::string debug_rep(T* p)
  8. {
  9. std::ostringstream ret;
  10. ret << "pointer: " << p;
  11. if(p)
  12. ret << " " << debug_rep(*p);
  13. else
  14. ret << " null pointer";
  15. return ret.str();
  16. }

练习16.49

解释下面每个调用会发生什么:

  1. template <typename T> void f(T);
  2. template <typename T> void f(const T*);
  3. template <typename T> void g(T);
  4. template <typename T> void g(T*);
  5. int i = 42, *p = &i;
  6. const int ci = 0, *p2 = &ci;
  7. g(42); g(p); g(ci); g(p2);
  8. f(42); f(p); f(ci); f(p2);

解:

  1. g(42); //g(T )
  2. g(p); //g(T*)
  3. g(ci); //g(T)
  4. g(p2); //g(T*)
  5. f(42); //f(T)
  6. f(p); //f(T)
  7. f(ci); //f(T)
  8. f(p2); //f(const T*)

练习16.50

定义上一个练习中的函数,令它们打印一条身份信息。运行该练习中的代码。如果函数调用的行为与你预期不符,确定你理解了原因。

解:

练习16.51

调用本节中的每个 foo,确定 sizeof...(Args)sizeof...(rest)分别返回什么。

解:

  1. #include <iostream>
  2. using namespace std;
  3. template <typename T, typename ... Args>
  4. void foo(const T &t, const Args& ... rest){
  5. cout << "sizeof...(Args): " << sizeof...(Args) << endl;
  6. cout << "sizeof...(rest): " << sizeof...(rest) << endl;
  7. };
  8. void test_param_packet(){
  9. int i = 0;
  10. double d = 3.14;
  11. string s = "how now brown cow";
  12. foo(i, s, 42, d);
  13. foo(s, 42, "hi");
  14. foo(d, s);
  15. foo("hi");
  16. }
  17. int main(){
  18. test_param_packet();
  19. return 0;
  20. }

结果:

  1. sizeof...(Args): 3
  2. sizeof...(rest): 3
  3. sizeof...(Args): 2
  4. sizeof...(rest): 2
  5. sizeof...(Args): 1
  6. sizeof...(rest): 1
  7. sizeof...(Args): 0
  8. sizeof...(rest): 0

练习16.52

编写一个程序验证上一题的答案。

解:

参考16.51。

练习16.53

编写你自己版本的 print 函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应有不同的类型。

解:

  1. template<typename Printable>
  2. std::ostream& print(std::ostream& os, Printable const& printable)
  3. {
  4. return os << printable;
  5. }
  6. // recursion
  7. template<typename Printable, typename... Args>
  8. std::ostream& print(std::ostream& os, Printable const& printable, Args const&... rest)
  9. {
  10. return print(os << printable << ", ", rest...);
  11. }

练习16.54

如果我们对一个没 << 运算符的类型调用 print,会发生什么?

解:

无法通过编译。

练习16.55

如果我们的可变参数版本 print 的定义之后声明非可变参数版本,解释可变参数的版本会如何执行。

解:

error: no matching function for call to 'print(std::ostream&)'

练习16.56

编写并测试可变参数版本的 errorMsg

解:

  1. template<typename... Args>
  2. std::ostream& errorMsg(std::ostream& os, const Args... rest)
  3. {
  4. return print(os, debug_rep(rest)...);
  5. }

练习16.57

比较你的可变参数版本的 errorMsg 和6.2.6节中的 error_msg函数。两种方法的优点和缺点各是什么?

解:

可变参数版本有更好的灵活性。

练习16.58

为你的 StrVec 类及你为16.1.2节练习中编写的 Vec 类添加 emplace_back 函数。

解:

  1. template<typename T> //for the class template
  2. template<typename... Args> //for the member template
  3. inline void
  4. Vec<T>::emplace_back(Args&&...args)
  5. {
  6. chk_n_alloc();
  7. alloc.construct(first_free++, std::forward<Args>(args)...);
  8. }

练习16.59

假定 s 是一个 string,解释调用 svec.emplace_back(s)会发生什么。

解:

会在 construst 函数中转发扩展包。

练习16.60

解释 make_shared 是如何工作的。

解:

make_shared 是一个可变模版函数,它将参数包转发然后构造一个对象,再然后一个指向该对象的智能指针。

练习16.61

定义你自己版本的 make_shared

解:

  1. template <typename T, typename ... Args>
  2. auto make_shared(Args&&... args) -> std::shared_ptr<T>
  3. {
  4. return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
  5. }

练习16.62

定义你自己版本的 hash<Sales_data>, 并定义一个 Sales_data 对象的 unorder_multise。将多条交易记录保存到容器中,并打印其内容。

解:

练习16.63

定义一个函数模版,统计一个给定值在一个vecor中出现的次数。测试你的函数,分别传递给它一个doublevector,一个intvector以及一个stringvector

解:

  1. #include <iostream>
  2. #include <vector>
  3. #include <cstring>
  4. // template
  5. template<typename T>
  6. std::size_t count(std::vector<T> const& vec, T value)
  7. {
  8. auto count = 0u;
  9. for(auto const& elem : vec)
  10. if(value == elem) ++count;
  11. return count;
  12. }
  13. // template specialization
  14. template<>
  15. std::size_t count (std::vector<const char*> const& vec, const char* value)
  16. {
  17. auto count = 0u;
  18. for(auto const& elem : vec)
  19. if(0 == strcmp(value, elem)) ++count;
  20. return count;
  21. }
  22. int main()
  23. {
  24. // for ex16.63
  25. std::vector<double> vd = { 1.1, 1.1, 2.3, 4 };
  26. std::cout << count(vd, 1.1) << std::endl;
  27. // for ex16.64
  28. std::vector<const char*> vcc = { "alan", "alan", "alan", "alan", "moophy" };
  29. std::cout << count(vcc, "alan") << std::endl;
  30. return 0;
  31. }

练习16.64

为上一题的模版编写特例化版本来处理vector<const char*>。编写程序使用这个特例化版本。

解:

参考16.64。

练习16.65

在16.3节中我们定义了两个重载的 debug_rep 版本,一个接受 const char* 参数,另一个接受 char * 参数。将这两个函数重写为特例化版本。

解:

  1. #include <iostream>
  2. #include <vector>
  3. #include <cstring>
  4. #include <sstream>
  5. // template
  6. template <typename T>
  7. std::string debug_rep(T* t);
  8. // template specialization T=const char* , char* respectively.
  9. template<>
  10. std::string debug_rep(const char* str);
  11. template<>
  12. std::string debug_rep( char *str);
  13. int main()
  14. {
  15. char p[] = "alan";
  16. std::cout << debug_rep(p) << "\n";
  17. return 0;
  18. }
  19. template <typename T>
  20. std::string debug_rep(T* t)
  21. {
  22. std::ostringstream ret;
  23. ret << t;
  24. return ret.str();
  25. }
  26. // template specialization
  27. // T = const char*
  28. template<>
  29. std::string debug_rep(const char* str)
  30. {
  31. std::string ret(str);
  32. return str;
  33. }
  34. // template specialization
  35. // T = char*
  36. template<>
  37. std::string debug_rep( char *str)
  38. {
  39. std::string ret(str);
  40. return ret;
  41. }

练习16.66

重载debug_rep 函数与特例化它相比,有何优点和缺点?

解:

重载函数会改变函数匹配。

练习16.67

定义特例化版本会影响 debug_rep 的函数匹配吗?如果不影响,为什么?

解:

不影响,特例化是模板的一个实例,并没有重载函数。