练习16.1
给出实例化的定义。
解:
当编译器实例化一个模版时,它使用实际的模版参数代替对应的模版参数来创建出模版的一个新“实例”。
练习16.2
编写并测试你自己版本的 compare
函数。
解:
1 2 3 4 5 6 7 template <typename T>int compare (const T& lhs, const T& rhs) { if (lhs < rhs) return -1 ; if (rhs < lhs) return 1 ; return 0 ; }
练习16.3
对两个 Sales_data
对象调用你的 compare
函数,观察编译器在实例化过程中如何处理错误。
解:
error: no match for 'operator<'
练习16.4
编写行为类似标准库 find
算法的模版。函数需要两个模版类型参数,一个表示函数的迭代器参数,另一个表示值的类型。使用你的函数在一个 vector<int>
和一个list<string>
中查找给定值。
解:
1 2 3 4 5 6 template <typename Iterator, typename Value>Iterator find (Iterator first, Iterator last, const Value& v) { for ( ; first != last && *first != value; ++first); return first; }
练习16.5
为6.2.4节中的print
函数编写模版版本,它接受一个数组的引用,能处理任意大小、任意元素类型的数组。
解:
1 2 3 4 5 6 template <typename Array>void print (const Array& arr) { for (const auto & elem : arr) std::cout << elem << std::endl; }
练习16.6
你认为接受一个数组实参的标准库函数 begin
和 end
是如何工作的?定义你自己版本的 begin
和 end
。
解:
1 2 3 4 5 6 7 8 9 10 11 template <typename T, unsigned N>T* begin (const T (&arr)[N]) { return arr; } template <typename T, unsigned N>T* end (const T (&arr)[N]) { return arr + N; }
练习16.7
编写一个 constexpr
模版,返回给定数组的大小。
解:
1 2 3 4 5 template <typename T, typename N> constexpr unsigned size (const T (&arr)[N]) { return N; }
练习16.8
在第97页的“关键概念”中,我们注意到,C++程序员喜欢使用 !=
而不喜欢 <
。解释这个习惯的原因。
解:
因为大多数类只定义了 !=
操作而没有定义 <
操作,使用 !=
可以降低对要处理的类型的要求。
练习16.9
什么是函数模版,什么是类模版?
解:
一个函数模版就是一个公式,可用来生成针对特定类型的函数版本。类模版是用来生成类的蓝图的,与函数模版的不同之处是,编译器不能为类模版推断模版参数类型。如果我们已经多次看到,为了使用类模版,我们必须在模版名后的尖括号中提供额外信息。
练习16.10
当一个类模版被实例化时,会发生什么?
解:
一个类模版的每个实例都形成一个独立的类。
练习16.11
下面 List
的定义是错误的。应如何修改它?
1 2 3 4 5 6 7 8 9 10 11 template <typename elemType> class ListItem ;template <typename elemType> class List {public : List <elemType>(); List <elemType>(const List<elemType> &); List<elemType>& operator =(const List<elemType> &); ~List (); void insert (ListItem *ptr, elemType value) ; private : ListItem *front, *end; };
解:
模版需要模版参数,应该修改为如下:
1 2 3 4 5 6 7 8 9 10 11 template <typename elemType> class ListItem ; template <typename elemType> class List { public : List <elemType>(); List <elemType>(const List<elemType> &); List<elemType>& operator =(const List<elemType> &); ~List (); void insert (ListItem<elemType> *ptr, elemType value) ; private : ListItem<elemType> *front, *end; };
练习16.12
编写你自己版本的 Blob
和 BlobPtr
模版,包含书中未定义的多个const
成员。
解:
Blob:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <memory> #include <vector> template <typename T> class Blob { public : typedef T value_type; typedef typename std::vector<T>::size_type size_type; Blob (); Blob (std::initializer_list<T> il); size_type size () const { return data->size (); } bool empty () const { return data->empty (); } void push_back (const T& t) { data->push_back (t); } void push_back (T&& t) { data->push_back (std::move (t)); } void pop_back () ; T& back () ; T& operator [](size_type i); const T& back () const ; const T& operator [](size_type i) const ; private : std::shared_ptr<std::vector<T>> data; void check (size_type i, const std::string &msg) const ; }; template <typename T>Blob<T>::Blob () : data (std::make_shared<std::vector<T>>()) {} template <typename T>Blob<T>::Blob (std::initializer_list<T> il) : data (std::make_shared<std::vector<T>>(il)){} template <typename T>void Blob<T>::check (size_type i, const std::string &msg) const { if (i >= data->size ()) throw std::out_of_range (msg); } template <typename T>T& Blob<T>::back () { check (0 , "back on empty Blob" ); return data->back (); } template <typename T>const T& Blob<T>::back () const { check (0 , "back on empty Blob" ); return data->back (); } template <typename T>T& Blob<T>::operator [](size_type i) { check (i, "subscript out of range" ); return (*data)[i]; } template <typename T>const T& Blob<T>::operator [](size_type i) const { check (i, "subscript out of range" ); return (*data)[i]; } template <typename T>void Blob<T>::pop_back (){ check (0 , "pop_back on empty Blob" ); data->pop_back (); }
BlobPtr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include "Blob.h" #include <memory> #include <vector> template <typename > class BlobPtr ;template <typename T>bool operator ==(const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);template <typename T>bool operator < (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);template <typename T> class BlobPtr { friend bool operator ==<T> (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs); friend bool operator < <T> (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs); public : BlobPtr () : curr (0 ) {} BlobPtr (Blob<T>& a, std::size_t sz = 0 ) : wptr (a.data), curr (sz) {} T& operator *() const { auto p = check (curr, "dereference past end" ); return (*p)[curr]; } BlobPtr& operator ++(); BlobPtr& operator --(); BlobPtr operator ++(int ); BlobPtr operator --(int ); private : std::shared_ptr<std::vector<T>> check (std::size_t , const std::string&) const ; std::weak_ptr<std::vector<T>> wptr; std::size_t curr; }; template <typename T>BlobPtr<T>& BlobPtr<T>::operator ++() { check (curr, "increment past end of StrBlob" ); ++curr; return *this ; } template <typename T>BlobPtr<T>& BlobPtr<T>::operator --() { --curr; check (curr, "decrement past begin of BlobPtr" ); return *this ; } template <typename T>BlobPtr<T> BlobPtr<T>::operator ++(int ) { BlobPtr ret = *this ; ++*this ; return ret; } template <typename T>BlobPtr<T> BlobPtr<T>::operator --(int ) { BlobPtr ret = *this ; --*this ; return ret; } template <typename T> bool operator ==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs){ if (lhs.wptr.lock () != rhs.wptr.lock ()) { throw runtime_error ("ptrs to different Blobs!" ); } return lhs.i == rhs.i; } template <typename T> bool operator < (const BlobPtr<T> &lhs, const BlobPtr<T> &rhs){ if (lhs.wptr.lock () != rhs.wptr.lock ()) { throw runtime_error ("ptrs to different Blobs!" ); } return lhs.i < rhs.i; }
练习16.13
解释你为 BlobPtr
的相等和关系运算符选择哪种类型的友好关系?
解:
这里需要与类型一一对应,所以就选择一对一友好关系。
练习16.14
编写 Screen
类模版,用非类型参数定义 Screen
的高和宽。
解:
Screen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <string> #include <iostream> template <unsigned H, unsigned W>class Screen { public : typedef std::string::size_type pos; Screen () = default ; Screen (char c) :contents (H * W, c) {} char get () const { return contents[cursor]; } Screen &move (pos r, pos c) ; friend std::ostream & operator << (std::ostream &os, const Screen<H, W> & c) { unsigned int i, j; for (i = 0 ; i<c.height; i++) { os << c.contents.substr (0 , W) << std::endl; } return os; } friend std::istream & operator >> (std::istream &is, Screen & c) { char a; is >> a; std::string temp (H*W, a) ; c.contents = temp; return is; } private : pos cursor = 0 ; pos height = H, width = W; std::string contents; }; template <unsigned H, unsigned W>inline Screen<H, W>& Screen<H, W>::move (pos r, pos c){ pos row = r * width; cursor = row + c; return *this ; }
练习16.15
为你的 Screen
模版实现输入和输出运算符。Screen
类需要哪些友元(如果需要的话)来令输入和输出运算符正确工作?解释每个友元声明(如果有的话)为什么是必要的。
解:
类的 operator<<
和 operator>>
应该是类的友元。
练习16.16
将 StrVec
类重写为模版,命名为 Vec
。
解:
Vec:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 #include <memory> template <typename T>class Vec { public : Vec () :element (nullptr ), first_free (nullptr ), cap (nullptr ) {} Vec (std::initializer_list<T> l); Vec (const Vec& v); Vec& operator =(const Vec& rhs); ~Vec (); void push_back (const T& t) ; std::size_t size () const { return first_free - element; } std::size_t capacity () const { return cap - element; } T* begin () const { return element; } T* end () const { return first_free; } void reserve (std::size_t n) ; void resize (std::size_t n) ; void resize (std::size_t n, const T& t) ; private : T* element; T* first_free; T* cap; std::allocator<T> alloc; void reallocate () ; void chk_n_alloc () { if (size () == capacity ()) reallocate (); } void free () ; void wy_alloc_n_move (std::size_t n) ; std::pair<T*, T*> alloc_n_copy (T* b, T* e) ; }; template <typename T>Vec<T>::Vec (const Vec &v) { std::pair<T*, T*> newData = alloc_n_copy (v.begin (), v.end ()); element = newData.first; first_free = cap = newData.second; } template <typename T>Vec<T>::Vec (std::initializer_list<T> l) { T* const newData = alloc.allocate (l.size ()); T* p = newData; for (const auto &t : l) alloc.construct (p++, t); element = newData; first_free = cap = element + l.size (); } template <typename T>Vec<T>& Vec<T>::operator =(const Vec& rhs) { std::pair<T*, T*> newData = alloc_n_copy (rhs.begin (), rhs.end ()); free (); element = newData.first; first_free = cap = newData.second; return *this ; } template <typename T>Vec<T>::~Vec () { free (); } template <typename T>void Vec<T>::push_back (const T &t){ chk_n_alloc (); alloc.construct (first_free++, t); } template <typename T>void Vec<T>::reserve (std::size_t n){ if (n <= capacity ()) return ; wy_alloc_n_move (n); } template <typename T>void Vec<T>::resize (std::size_t n){ resize (n, T ()); } template <typename T>void Vec<T>::resize (std::size_t n, const T &t){ if (n < size ()) { for (auto p = element + n; p != first_free;) alloc.destroy (p++); first_free = element + n; } else if (n > size ()) { for (auto i = size (); i != n; ++i) push_back (t); } } template <typename T>std::pair<T*, T*> Vec<T>::alloc_n_copy (T *b, T *e) { T* data = alloc.allocate (e - b); return { data, std::uninitialized_copy (b, e, data) }; } template <typename T>void Vec<T>::free (){ if (element) { for (auto p = first_free; p != element;) alloc.destroy (--p); alloc.deallocate (element, capacity ()); } } template <typename T>void Vec<T>::wy_alloc_n_move (std::size_t n){ std::size_t newCapacity = n; T* newData = alloc.allocate (newCapacity); T* dest = newData; T* old = element; for (std::size_t i = 0 ; i != size (); ++i) alloc.construct (dest++, std::move (*old++)); free (); element = newData; first_free = dest; cap = element + newCapacity; } template <typename T>void Vec<T>::reallocate (){ std::size_t newCapacity = size () ? 2 * size () : 1 ; wy_alloc_n_move (newCapacity); }
练习16.17
声明为 typename
的类型参数和声明为 class
的类型参数有什么不同(如果有的话)?什么时候必须使用typename
?
解:
没有什么不同。当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename
,而不能使用 class
。
练习16.18
解释下面每个函数模版声明并指出它们是否非法。更正你发现的每个错误。
1 2 3 4 5 6 (a) template <typename T, U, typename V> void f1 (T, U, V) ; (b) template <typename T> T f2 (int &T) ; (c) inline template <typename T> T foo (T, unsigned int *) ; (d) template <typename T> f4 (T, T); (e) typedef char Ctype; 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);
© 非法。应该为 template <typename T> inline T foo(T, unsigned int*);
(d) 非法。应该为 template <typename T> T f4(T, T);
(e) 非法。Ctype
被隐藏了。
练习16.19
编写函数,接受一个容器的引用,打印容器中的元素。使用容器的 size_type
和 size
成员来控制打印元素的循环。
解:
1 2 3 4 5 6 template <typename Container>void print (const Container& c) { for (typename Container::size_type i = 0 ; i != c.size (); ++i) std::cout << c[i] << " " ; }
练习16.20
重写上一题的函数,使用begin
和 end
返回的迭代器来控制循环。
解:
1 2 3 4 5 6 template <typename Container>void print (const Container& c) { for (auto it = c.begin (); it != c.end (); ++it) std::cout << *it << " " ; }
练习16.21
编写你自己的 DebugDelete
版本。
解:
DebugDelete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> class DebugDelete { public : DebugDelete (std::ostream& s = std::cerr) : os (s) {} template <typename T> void operator () (T* p) const { os << "deleting unique_ptr" << std::endl; delete p; } private : std::ostream& os; };
练习16.22
修改12.3节中你的 TextQuery
程序,令 shared_ptr
成员使用 DebugDelete
作为它们的删除器。
解:
略
练习16.23
预测在你的查询主程序中何时会执行调用运算符。如果你的预测和实际不符,确认你理解了原因。
解:
略
练习16.24
为你的 Blob
模版添加一个构造函数,它接受两个迭代器。
解:
1 2 3 4 5 template <typename T> template <typename It> Blob<T>::Blob (It b, It e) : data (std::make_shared<std::vector<T>>(b, e)) { }
练习16.25
解释下面这些声明的含义。
1 2 extern template class vector <string>;template class vector <Sales_data>;
解:
前者是模版声明,后者是实例化定义。
练习16.26
假设 NoDefault
是一个没有默认构造函数的类,我们可以显式实例化 vector<NoDefualt>
吗?如果不可以,解释为什么。
解:
不可以。如
1 std::vector<NoDefault> vec (10 ) ;
会使用 NoDefault
的默认构造函数,而 NoDefault
没有默认构造函数,因此是不可以的。
练习16.27
对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模版被实例化,解释为什么;如果未实例化,解释为什么没有。
1 2 3 4 5 6 7 8 9 10 11 template <typename T> class Stack { };void f1 (Stack<char >) ; class Exercise { Stack<double > &rds; Stack<int > si; }; int main () { Stack<char > *sc; f1 (*sc); int iObj = sizeof (Stack<string>); }
解:
(a)、(b)、©、(f) 都发生了实例化,(d)、(e) 没有实例化。
练习16.28
编写你自己版本的 shared_ptr
和 unique_ptr
。
解:
shared_ptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 #pragma once #include <functional> #include "delete.h" namespace cp5{ template <typename T> class SharedPointer ; template <typename T> auto swap (SharedPointer<T>& lhs, SharedPointer<T>& rhs) { using std::swap; swap (lhs.ptr, rhs.ptr); swap (lhs.ref_count, rhs.ref_count); swap (lhs.deleter, rhs.deleter); } template <typename T> class SharedPointer { public : SharedPointer () : ptr{ nullptr }, ref_count{ new std::size_t (1 ) }, deleter{ cp5::Delete{} } {} explicit SharedPointer (T* raw_ptr) : ptr{ raw_ptr }, ref_count{ new std::size_t (1 ) }, deleter{ cp5::Delete{} } {} SharedPointer (SharedPointer const & other) : ptr{ other.ptr }, ref_count{ other.ref_count }, deleter{ other.deleter } { ++*ref_count; } SharedPointer (SharedPointer && other) noexcept : ptr{ other.ptr }, ref_count{ other.ref_count }, deleter{ std::move (other.deleter) } { other.ptr = nullptr ; other.ref_count = nullptr ; } SharedPointer& operator =(SharedPointer const & rhs) { ++*rhs.ref_count; decrement_and_destroy (); ptr = rhs.ptr, ref_count = rhs.ref_count, deleter = rhs.deleter; return *this ; } SharedPointer& operator =(SharedPointer && rhs) noexcept { cp5::swap (*this , rhs); rhs.decrement_and_destroy (); return *this ; } operator bool () const { return ptr ? true : false ; } T& operator * () const { return *ptr; } T* operator ->() const { return &*ptr; } auto use_count () const { return *ref_count; } auto get () const { return ptr; } auto unique () const { return 1 == *refCount; } auto swap (SharedPointer& rhs) { ::swap (*this , rhs); } auto reset () { decrement_and_destroy (); } auto reset (T* pointer) { if (ptr != pointer) { decrement_n_destroy (); ptr = pointer; ref_count = new std::size_t (1 ); } } auto reset (T *pointer, const std::function<void (T*)>& d) { reset (pointer); deleter = d; } ~SharedPointer () { decrement_and_destroy (); } private : T* ptr; std::size_t * ref_count; std::function<void (T*)> deleter; auto decrement_and_destroy () { if (ptr && 0 == --*ref_count) delete ref_count, deleter (ptr); else if (!ptr) delete ref_count; ref_count = nullptr ; ptr = nullptr ; } }; }
unique_ptr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include "debugDelete.h" template <typename , typename > class unique_pointer ;template <typename T, typename D> void swap (unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs) ;template <typename T, typename D = DebugDelete>class unique_pointer{ friend void swap <T, D>(unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs); public : unique_pointer (const unique_pointer&) = delete ; unique_pointer& operator = (const unique_pointer&) = delete ; unique_pointer () = default ; explicit unique_pointer (T* up) : ptr(up) { } unique_pointer (unique_pointer&& up) noexcept : ptr (up.ptr) { up.ptr = nullptr ; } unique_pointer& operator =(unique_pointer&& rhs) noexcept ; unique_pointer& operator =(std::nullptr_t n) noexcept ; T& operator *() const { return *ptr; } T* operator ->() const { return &this ->operator *(); } operator bool () const { return ptr ? true : false ; } T* get () const noexcept { return ptr; } void swap (unique_pointer<T, D> &rhs) { ::swap (*this , rhs); } void reset () noexcept { deleter (ptr); ptr = nullptr ; } void reset (T* p) noexcept { deleter (ptr); ptr = p; } T* release () ; ~unique_pointer () { deleter (ptr); } private : T* ptr = nullptr ; D deleter = D (); }; template <typename T, typename D>inline void swap (unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs) { using std::swap; swap (lhs.ptr, rhs.ptr); swap (lhs.deleter, rhs.deleter); } template <typename T, typename D>inline unique_pointer<T, D>&unique_pointer<T, D>::operator =(unique_pointer&& rhs) noexcept { if (this ->ptr != rhs.ptr) { deleter (ptr); ptr = nullptr ; ::swap (*this , rhs); } return *this ; } template <typename T, typename D>inline unique_pointer<T, D>&unique_pointer<T, D>::operator =(std::nullptr_t n) noexcept { if (n == nullptr ) { deleter (ptr); ptr = nullptr ; } return *this ; } template <typename T, typename D>inline T*unique_pointer<T, D>::release () { T* ret = ptr; ptr = nullptr ; return ret; }
练习16.29
修改你的 Blob
类,用你自己的 shared_ptr
代替标准库中的版本。
解:
略
练习16.30
重新运行你的一些程序,验证你的 shared_ptr
类和修改后的 Blob
类。(注意:实现 weak_ptr
类型超出了本书范围,因此你不能将BlobPtr
类与你修改后的Blob
一起使用。)
解:
略
练习16.31
如果我们将 DebugDelete
与 unique_ptr
一起使用,解释编译器将删除器处理为内联形式的可能方式。
解:
略
练习16.32
在模版实参推断过程中发生了什么?
解:
在模版实参推断过程中,编译器使用函数调用中的实参类型来寻找模版实参,用这些模版实参生成的函数版本与给定的函数调用最为匹配。
练习16.33
指出在模版实参推断过程中允许对函数实参进行的两种类型转换。
解:
const
转换:可以将一个非 const
对象的引用(或指针)传递给一个 const
的引用(或指针)形参。
数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。
练习16.34
对下面的代码解释每个调用是否合法。如果合法,T
的类型是什么?如果不合法,为什么?
1 2 3 template <class T > int compare (const T&, const T&) ;(a) compare ("hi" , "world" ); (b) compare ("bye" , "dad" );
解:
(a) 不合法。compare(const char [3], const char [6])
, 两个实参类型不一致。
(b) 合法。compare(const char [4], const char [4])
.
练习16.35
下面调用中哪些是错误的(如果有的话)?如果调用合法,T
的类型是什么?如果调用不合法,问题何在?
1 2 3 4 5 6 7 template <typename T> T calc (T, int ) ;tempalte <typename T> T fcn (T, T) ; double d; float f; char c;(a) calc (c, 'c' ); (b) calc (d, f); (c) fcn (c, 'c' ); (d) fcn (d, f);
解:
(a) 合法,类型为char
(b) 合法,类型为double
© 合法,类型为char
(d) 不合法,这里无法确定T的类型是float
还是double
练习16.36
进行下面的调用会发生什么:
1 2 3 4 5 6 7 8 9 10 template <typename T> f1 (T, T);template <typename T1, typename T2) f2 (T1, T2);int i = 0 , j = 42 , *p1 = &i, *p2 = &j;const int *cp1 = &i, *cp2 = &j;(a) f1 (p1, p2); (b) f2 (p1, p2); (c) f1 (cp1, cp2); (d) f2 (cp1, cp2); (e) f1 (p1, cp1); (f) f2 (p1, cp1);
解:
1 2 3 4 5 6 (a) f1 (int *, int *); (b) f2 (int *, int *); (c) f1 (const int *, const int *); (d) f2 (const int *, const int *); (e) f1 (int *, const int *); 这个使用就不合法 (f) f2 (int *, const int *);
练习16.37
标准库 max
函数有两个参数,它返回实参中的较大者。此函数有一个模版类型参数。你能在调用 max
时传递给它一个 int
和一个 double
吗?如果可以,如何做?如果不可以,为什么?
解:
可以。提供显式的模版实参:
1 2 3 int a = 1 ;double b = 2 ;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 2 3 4 5 6 template <typename It>auto fcn3 (It beg, It end) -> decltype (*beg + 0 ) { return *beg; }
解:
合法。该类型需要支持 +
操作。
练习16.41
编写一个新的 sum
版本,它返回类型保证足够大,足以容纳加法结果。
解:
1 2 3 4 5 6 template <typename T>auto sum (T lhs, T rhs) -> decltype ( lhs + rhs) { return lhs + rhs; }
练习16.42
对下面每个调用,确定 T
和 val
的类型:
1 2 3 4 5 template <typename T> void g (T&& val) ;int i = 0 ; const int ci = i;(a) g (i); (b) g (ci); (c) g (i * ci);
解:
1 2 3 (a) int & (b) const int & (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 2 for (size_t i = 0 ; i != size (); ++i) alloc.construct (dest++, std::move (*elem++));
解:
在每个循环中,对 elem
的解引用操作 *
当中,会返回一个左值,std::move
函数将该左值转换为右值,提供给 construct
函数。
练习16.47
编写你自己版本的翻转函数,通过调用接受左值和右值引用参数的函数来测试它。
解:
1 2 3 4 5 template <typename F, typename T1, typename T2>void flip (F f, T1&& t1, T2&& t2) { f (std::forward<T2>(t2), std::forward<T1>(t1)); }
练习16.48
编写你自己版本的 debug_rep
函数。
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 template <typename T> std::string debug_rep (const T& t) { std::ostringstream ret; ret << t; return ret.str (); } template <typename T> std::string debug_rep (T* p) { std::ostringstream ret; ret << "pointer: " << p; if (p) ret << " " << debug_rep (*p); else ret << " null pointer" ; return ret.str (); }
练习16.49
解释下面每个调用会发生什么:
1 2 3 4 5 6 7 8 template <typename T> void f (T) ;template <typename T> void f (const T*) ;template <typename T> void g (T) ;template <typename T> void g (T*) ;int i = 42 , *p = &i;const int ci = 0 , *p2 = &ci;g (42 ); g (p); g (ci); g (p2);f (42 ); f (p); f (ci); f (p2);
解:
1 2 3 4 5 6 7 8 g (42 ); g (p); g (ci); g (p2); f (42 ); f (p); f (ci); f (p2);
练习16.50
定义上一个练习中的函数,令它们打印一条身份信息。运行该练习中的代码。如果函数调用的行为与你预期不符,确定你理解了原因。
解:
略
练习16.51
调用本节中的每个 foo
,确定 sizeof…(Args)
和 sizeof…(rest)
分别返回什么。
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;template <typename T, typename ... Args>void foo (const T &t, const Args& ... rest) { cout << "sizeof...(Args): " << sizeof ...(Args) << endl; cout << "sizeof...(rest): " << sizeof ...(rest) << endl; }; void test_param_packet () { int i = 0 ; double d = 3.14 ; string s = "how now brown cow" ; foo (i, s, 42 , d); foo (s, 42 , "hi" ); foo (d, s); foo ("hi" ); } int main () { test_param_packet (); return 0 ; }
结果:
1 2 3 4 5 6 7 8 sizeof...(Args): 3 sizeof...(rest): 3 sizeof...(Args): 2 sizeof...(rest): 2 sizeof...(Args): 1 sizeof...(rest): 1 sizeof...(Args): 0 sizeof...(rest): 0
练习16.52
编写一个程序验证上一题的答案。
解:
参考16.51。
练习16.53
编写你自己版本的 print
函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应有不同的类型。
解:
1 2 3 4 5 6 7 8 9 10 11 template <typename Printable>std::ostream& print (std::ostream& os, Printable const & printable) { return os << printable; } template <typename Printable, typename ... Args>std::ostream& print (std::ostream& os, Printable const & printable, Args const &... rest) { return print (os << printable << ", " , rest...); }
练习16.54
如果我们对一个没 <<
运算符的类型调用 print
,会发生什么?
解:
无法通过编译。
练习16.55
如果我们的可变参数版本 print
的定义之后声明非可变参数版本,解释可变参数的版本会如何执行。
解:
error: no matching function for call to 'print(std::ostream&)'
练习16.56
编写并测试可变参数版本的 errorMsg
。
解:
1 2 3 4 5 template <typename ... Args>std::ostream& errorMsg (std::ostream& os, const Args... rest) { return print (os, debug_rep (rest)...); }
练习16.57
比较你的可变参数版本的 errorMsg
和6.2.6节中的 error_msg
函数。两种方法的优点和缺点各是什么?
解:
可变参数版本有更好的灵活性。
练习16.58
为你的 StrVec
类及你为16.1.2节练习中编写的 Vec
类添加 emplace_back
函数。
解:
1 2 3 4 5 6 7 8 template <typename T> template <typename ... Args> inline void Vec<T>::emplace_back (Args&&...args) { chk_n_alloc (); alloc.construct (first_free++, std::forward<Args>(args)...); }
练习16.59
假定 s
是一个 string
,解释调用 svec.emplace_back(s)
会发生什么。
解:
会在 construst
函数中转发扩展包。
练习16.60
解释 make_shared
是如何工作的。
解:
make_shared
是一个可变模版函数,它将参数包转发然后构造一个对象,再然后一个指向该对象的智能指针。
练习16.61
定义你自己版本的 make_shared
。
解:
1 2 3 4 5 template <typename T, typename ... Args>auto make_shared (Args&&... args) -> std::shared_ptr<T> { return std::shared_ptr <T>(new T (std::forward<Args>(args)...)); }
练习16.62
定义你自己版本的 hash<Sales_data>
, 并定义一个 Sales_data
对象的 unorder_multise
。将多条交易记录保存到容器中,并打印其内容。
解:
略
练习16.63
定义一个函数模版,统计一个给定值在一个vecor
中出现的次数。测试你的函数,分别传递给它一个double
的vector
,一个int
的vector
以及一个string
的vector
。
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <vector> #include <cstring> template <typename T>std::size_t count (std::vector<T> const & vec, T value) { auto count = 0u ; for (auto const & elem : vec) if (value == elem) ++count; return count; } template <>std::size_t count (std::vector<const char *> const & vec, const char * value) { auto count = 0u ; for (auto const & elem : vec) if (0 == strcmp (value, elem)) ++count; return count; } int main () { std::vector<double > vd = { 1.1 , 1.1 , 2.3 , 4 }; std::cout << count (vd, 1.1 ) << std::endl; std::vector<const char *> vcc = { "alan" , "alan" , "alan" , "alan" , "moophy" }; std::cout << count (vcc, "alan" ) << std::endl; return 0 ; }
练习16.64
为上一题的模版编写特例化版本来处理vector<const char*>
。编写程序使用这个特例化版本。
解:
参考16.64。
练习16.65
在16.3节中我们定义了两个重载的 debug_rep
版本,一个接受 const char*
参数,另一个接受 char *
参数。将这两个函数重写为特例化版本。
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <vector> #include <cstring> #include <sstream> template <typename T>std::string debug_rep (T* t) ;template <>std::string debug_rep (const char * str) ;template <>std::string debug_rep ( char *str) ;int main () { char p[] = "alan" ; std::cout << debug_rep (p) << "\n" ; return 0 ; } template <typename T>std::string debug_rep (T* t) { std::ostringstream ret; ret << t; return ret.str (); } template <>std::string debug_rep (const char * str) { std::string ret (str) ; return str; } template <>std::string debug_rep ( char *str) { std::string ret (str) ; return ret; }
练习16.66
重载debug_rep
函数与特例化它相比,有何优点和缺点?
解:
重载函数会改变函数匹配。
练习16.67
定义特例化版本会影响 debug_rep
的函数匹配吗?如果不影响,为什么?
解:
不影响,特例化是模板的一个实例,并没有重载函数。