- 练习16.1
- 练习16.2
- 练习16.3
- 练习16.4
- 练习16.5
- 练习16.6
- 练习16.7
- 练习16.8
- 练习16.9
- 练习16.10
- 练习16.11
- 练习16.12
- 练习16.13
- 练习16.14
- 练习16.15
- 练习16.16
- 练习16.17
- 练习16.18
- 练习16.19
- 练习16.20
- 练习16.21
- 练习16.22
- 练习16.23
- 练习16.24
- 练习16.25
- 练习16.26
- 练习16.27
- 练习16.28
- 练习16.29
- 练习16.30
- 练习16.31
- 练习16.32
- 练习16.33
- 练习16.34
- 练习16.35
- 练习16.36
- 练习16.37
- 练习16.38
- 练习16.39
- 练习16.40
- 练习16.41
- 练习16.42
- 练习16.43
- 练习16.44
- 练习16.45
- 练习16.46
- 练习16.47
- 练习16.48
- 练习16.49
- 练习16.50
- 练习16.51
- 练习16.52
- 练习16.53
- 练习16.54
- 练习16.55
- 练习16.56
- 练习16.57
- 练习16.58
- 练习16.59
- 练习16.60
- 练习16.61
- 练习16.62
- 练习16.63
- 练习16.64
- 练习16.65
- 练习16.66
- 练习16.67
练习16.1
给出实例化的定义。
解:
当编译器实例化一个模版时,它使用实际的模版参数代替对应的模版参数来创建出模版的一个新“实例”。
练习16.2
编写并测试你自己版本的
compare函数。
解:
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>中查找给定值。
解:
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节中的
解:
template<typename Array>void print(const Array& arr){for (const auto& elem : arr)std::cout << elem << std::endl;}
练习16.6
你认为接受一个数组实参的标准库函数
begin和end是如何工作的?定义你自己版本的begin和end。
解:
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模版,返回给定数组的大小。
解:
template<typename T, typename N> constexprunsigned size(const T (&arr)[N]){return N;}
练习16.8
在第97页的“关键概念”中,我们注意到,C++程序员喜欢使用
!=而不喜欢<。解释这个习惯的原因。
解:
因为大多数类只定义了 != 操作而没有定义 < 操作,使用 != 可以降低对要处理的类型的要求。
练习16.9
什么是函数模版,什么是类模版?
解:
一个函数模版就是一个公式,可用来生成针对特定类型的函数版本。类模版是用来生成类的蓝图的,与函数模版的不同之处是,编译器不能为类模版推断模版参数类型。如果我们已经多次看到,为了使用类模版,我们必须在模版名后的尖括号中提供额外信息。
练习16.10
当一个类模版被实例化时,会发生什么?
解:
一个类模版的每个实例都形成一个独立的类。
练习16.11
下面
List的定义是错误的。应如何修改它?
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;};
解:
模版需要模版参数,应该修改为如下:
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:
#include <memory>#include <vector>template<typename T> class Blob{public:typedef T value_type;typedef typename std::vector<T>::size_type size_type;// constructorsBlob();Blob(std::initializer_list<T> il);// number of elements in the Blobsize_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();// element accessT& 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;// throw msg if data[i] isn't validvoid check(size_type i, const std::string &msg) const;};// constructorstemplate<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){// if i is too big, check function will throw, preventing access to a nonexistent elementcheck(i, "subscript out of range");return (*data)[i];}template<typename T>const T& Blob<T>::operator [](size_type i) const{// if i is too big, check function will throw, preventing access to a nonexistent elementcheck(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:
#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];}// prefixBlobPtr& operator++();BlobPtr& operator--();// postfixBlobPtr operator ++(int);BlobPtr operator --(int);private:// returns a shared_ptr to the vector if the check succeedsstd::shared_ptr<std::vector<T>>check(std::size_t, const std::string&) const;std::weak_ptr<std::vector<T>> wptr;std::size_t curr;};// prefix ++template<typename T>BlobPtr<T>& BlobPtr<T>::operator ++(){// if curr already points past the end of the container, can't increment itcheck(curr, "increment past end of StrBlob");++curr;return *this;}// prefix --template<typename T>BlobPtr<T>& BlobPtr<T>::operator --(){--curr;check(curr, "decrement past begin of BlobPtr");return *this;}// postfix ++template<typename T>BlobPtr<T> BlobPtr<T>::operator ++(int){BlobPtr ret = *this;++*this;return ret;}// postfix --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
#include <string>#include <iostream>template<unsigned H, unsigned W>class Screen{public:typedef std::string::size_type pos;Screen() = default; // needed because Screen has another constructor// cursor initialized to 0 by its in-class initializerScreen(char c) :contents(H * W, c) {}char get() const // get the character at the cursor{return contents[cursor];} // implicitly inlineScreen &move(pos r, pos c); // can be made inline laterfriend 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:
#include <memory>/*** @brief a vector like class*/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();// memmbersvoid 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:// data membersT* element;T* first_free;T* cap;std::allocator<T> alloc;// utillitiesvoid 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);};// copy constructortemplate<typename T>Vec<T>::Vec(const Vec &v){/*** @brief newData is a pair of pointers pointing to newly allocated and copied* from range : [b, e)*/std::pair<T*, T*> newData = alloc_n_copy(v.begin(), v.end());element = newData.first;first_free = cap = newData.second;}// constructor that takes initializer_list<T>template<typename T>Vec<T>::Vec(std::initializer_list<T> l){// allocate memory as large as l.size()T* const newData = alloc.allocate(l.size());// copy elements from l to the address allocatedT* p = newData;for (const auto &t : l)alloc.construct(p++, t);// build data structureelement = newData;first_free = cap = element + l.size();}// operator =template<typename T>Vec<T>& Vec<T>::operator =(const Vec& rhs){// allocate and copy first to protect against self_assignmentstd::pair<T*, T*> newData = alloc_n_copy(rhs.begin(), rhs.end());// destroy and deallocatefree();// update data structureelement = newData.first;first_free = cap = newData.second;return *this;}// destructortemplate<typename T>Vec<T>::~Vec(){free();}/*** @brief allocate new memeory if nessary and push back the new T* @param t new T*/template<typename T>void Vec<T>::push_back(const T &t){chk_n_alloc();alloc.construct(first_free++, t);}/*** @brief preallocate enough memory for specified number of elements* @param n number of elements required*/template<typename T>void Vec<T>::reserve(std::size_t n){// if n too small, just return without doing anythingif (n <= capacity()) return;// allocate new memory and move data from old address to the new onewy_alloc_n_move(n);}/*** @brief Resizes to the specified number of elements.* @param n Number of elements the %vector should contain.** This function will resize it to the specified* number of elements. If the number is smaller than the* current size it is truncated, otherwise* default constructed elements are appended.*/template<typename T>void Vec<T>::resize(std::size_t n){resize(n, T());}/*** @brief Resizes it to the specified number of elements.* @param __new_size Number of elements it should contain.* @param __x Data with which new elements should be populated.** This function will resize it to the specified* number of elements. If the number is smaller than the* current size the it is truncated, otherwise* the it is extended and new elements are populated with* given data.*/template<typename T>void Vec<T>::resize(std::size_t n, const T &t){if (n < size()){// destroy the range [element+n, first_free) using destructorfor (auto p = element + n; p != first_free;)alloc.destroy(p++);// update first_free to point to the new addressfirst_free = element + n;}else if (n > size()){for (auto i = size(); i != n; ++i)push_back(t);}}/*** @brief allocate new space for the given range and copy them into it* @param b* @param e* @return a pair of pointers pointing to [first element , one past the last) in the new space*/template<typename T>std::pair<T*, T*>Vec<T>::alloc_n_copy(T *b, T *e){// calculate the size needed and allocate space accordinglyT* data = alloc.allocate(e - b);return{ data, std::uninitialized_copy(b, e, data) };// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^// which copies the range[first, last) to the space to which// the starting address data is pointing.// This function returns a pointer to one past the last element}/*** @brief destroy the elements and deallocate the space previously allocated.*/template<typename T>void Vec<T>::free(){// if not nullptrif (element){// destroy it in reverse order.for (auto p = first_free; p != element;)alloc.destroy(--p);alloc.deallocate(element, capacity());}}/*** @brief allocate memory for spicified number of elements* @param n* @note it's user's responsibility to ensure that @param n is greater than* the current capacity.*/template<typename T>void Vec<T>::wy_alloc_n_move(std::size_t n){// allocate as required.std::size_t newCapacity = n;T* newData = alloc.allocate(newCapacity);// move the data from old place to the new oneT* dest = newData;T* old = element;for (std::size_t i = 0; i != size(); ++i)alloc.construct(dest++, std::move(*old++));free();// update data structureelement = newData;first_free = dest;cap = element + newCapacity;}/*** @brief Double the capacity and using std::move move the original data to the newly* allocated memory*/template<typename T>void Vec<T>::reallocate(){// calculate the new capacity requiredstd::size_t newCapacity = size() ? 2 * size() : 1;// allocate and move old data to the new spacewy_alloc_n_move(newCapacity);}
练习16.17
声明为
typename的类型参数和声明为class的类型参数有什么不同(如果有的话)?什么时候必须使用typename?
解:
没有什么不同。当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename,而不能使用 class。
练习16.18
解释下面每个函数模版声明并指出它们是否非法。更正你发现的每个错误。
(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); - (c) 非法。应该为
template <typename T> inline T foo(T, unsigned int*); - (d) 非法。应该为
template <typename T> T f4(T, T); - (e) 非法。
Ctype被隐藏了。
练习16.19
编写函数,接受一个容器的引用,打印容器中的元素。使用容器的
size_type和size成员来控制打印元素的循环。
解:
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返回的迭代器来控制循环。
解:
template<typename Container>void print(const Container& c){for (auto it = c.begin(); it != c.end(); ++it)std::cout << *it << " ";}
练习16.21
编写你自己的
DebugDelete版本。
解:
DebugDelete
#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模版添加一个构造函数,它接受两个迭代器。
解:
template<typename T> //for classtemplate<typename It> //for this memberBlob<T>::Blob(It b, It e) :data(std::make_shared<std::vector<T>>(b, e)){ }
练习16.25
解释下面这些声明的含义。
extern template class vector<string>;template class vector<Sales_data>;
解:
前者是模版声明,后者是实例化定义。
练习16.26
假设
NoDefault是一个没有默认构造函数的类,我们可以显式实例化vector<NoDefualt>吗?如果不可以,解释为什么。
解:
不可以。如
std::vector<NoDefault> vec(10);
会使用 NoDefault 的默认构造函数,而 NoDefault 没有默认构造函数,因此是不可以的。
练习16.27
对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模版被实例化,解释为什么;如果未实例化,解释为什么没有。
template <typename T> class Stack { };void f1(Stack<char>); //(a)class Exercise {Stack<double> &rds; //(b)Stack<int> si; //(c)};int main() {Stack<char> *sc; //(d)f1(*sc); //(e)int iObj = sizeof(Stack<string>); //(f)}
解:
(a)、(b)、(c)、(f) 都发生了实例化,(d)、(e) 没有实例化。
练习16.28
编写你自己版本的
shared_ptr和unique_ptr。
解:
shared_ptr
#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://// Default Ctor//SharedPointer(): ptr{ nullptr }, ref_count{ new std::size_t(1) }, deleter{ cp5::Delete{} }{}//// Ctor that takes raw pointer//explicit SharedPointer(T* raw_ptr): ptr{ raw_ptr }, ref_count{ new std::size_t(1) }, deleter{ cp5::Delete{} }{}//// Copy Ctor//SharedPointer(SharedPointer const& other): ptr{ other.ptr }, ref_count{ other.ref_count }, deleter{ other.deleter }{++*ref_count;}//// Move Ctor//SharedPointer(SharedPointer && other) noexcept: ptr{ other.ptr }, ref_count{ other.ref_count }, deleter{ std::move(other.deleter) }{other.ptr = nullptr;other.ref_count = nullptr;}//// Copy assignment//SharedPointer& operator=(SharedPointer const& rhs){//increment first to ensure safty for self-assignment++*rhs.ref_count;decrement_and_destroy();ptr = rhs.ptr, ref_count = rhs.ref_count, deleter = rhs.deleter;return *this;}//// Move assignment//SharedPointer& operator=(SharedPointer && rhs) noexcept{cp5::swap(*this, rhs);rhs.decrement_and_destroy();return *this;}//// Conversion operator//operator bool() const{return ptr ? true : false;}//// Dereference//T& operator* () const{return *ptr;}//// Arrow//T* operator->() const{return &*ptr;}//// Use count//auto use_count() const{return *ref_count;}//// Get underlying pointer//auto get() const{return ptr;}//// Check if the unique user//auto unique() const{return 1 == *refCount;}//// Swap//auto swap(SharedPointer& rhs){::swap(*this, rhs);}//// Free the object pointed to, if unique//auto reset(){decrement_and_destroy();}//// Reset with the new raw pointer//auto reset(T* pointer){if (ptr != pointer){decrement_n_destroy();ptr = pointer;ref_count = new std::size_t(1);}}//// Reset with raw pointer and deleter//auto reset(T *pointer, const std::function<void(T*)>& d){reset(pointer);deleter = d;}//// Dtor//~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;}};}//namespace
unique_ptr:
#include "debugDelete.h"// forward declarations for friendshiptemplate<typename, typename> class unique_pointer;template<typename T, typename D> voidswap(unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs);/*** @brief std::unique_ptr like class template.*/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:// preventing copy and assignmentunique_pointer(const unique_pointer&) = delete;unique_pointer& operator = (const unique_pointer&) = delete;// default constructor and one taking T*unique_pointer() = default;explicit unique_pointer(T* up) : ptr(up) {}// move constructorunique_pointer(unique_pointer&& up) noexcept: ptr(up.ptr) { up.ptr = nullptr; }// move assignmentunique_pointer& operator =(unique_pointer&& rhs) noexcept;// std::nullptr_t assignmentunique_pointer& operator =(std::nullptr_t n) noexcept;// operator overloaded : * -> boolT& operator *() const { return *ptr; }T* operator ->() const { return &this->operator *(); }operator bool() const { return ptr ? true : false; }// return the underlying pointerT* get() const noexcept{ return ptr; }// swap member using swap friendvoid swap(unique_pointer<T, D> &rhs) { ::swap(*this, rhs); }// free and make it point to nullptr or to p's pointee.void reset() noexcept{ deleter(ptr); ptr = nullptr; }void reset(T* p) noexcept{ deleter(ptr); ptr = p; }// return ptr and make ptr point to nullptr.T* release();~unique_pointer(){deleter(ptr);}private:T* ptr = nullptr;D deleter = D();};// swaptemplate<typename T, typename D>inline voidswap(unique_pointer<T, D>& lhs, unique_pointer<T, D>& rhs){using std::swap;swap(lhs.ptr, rhs.ptr);swap(lhs.deleter, rhs.deleter);}// move assignmenttemplate<typename T, typename D>inline unique_pointer<T, D>&unique_pointer<T, D>::operator =(unique_pointer&& rhs) noexcept{// prevent self-assignmentif (this->ptr != rhs.ptr){deleter(ptr);ptr = nullptr;::swap(*this, rhs);}return *this;}// std::nullptr_t assignmenttemplate<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;}// relinquish contrul by returnning ptr and making ptr point to nullptr.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的类型是什么?如果不合法,为什么?
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的类型是什么?如果调用不合法,问题何在?
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 - (c) 合法,类型为
char - (d) 不合法,这里无法确定T的类型是
float还是double
练习16.36
进行下面的调用会发生什么:
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);
解:
(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吗?如果可以,如何做?如果不可以,为什么?
解:
可以。提供显式的模版实参:
int a = 1;double b = 2;std::max<double>(a, b);
练习16.38
当我们调用
make_shared时,必须提供一个显示模版实参。解释为什么需要显式模版实参以及它是如果使用的。
解:
如果不显示提供模版实参,那么 make_shared 无法推断要分配多大内存空间。
练习16.39
对16.1.1节 中的原始版本的
compare函数,使用一个显式模版实参,使得可以向函数传递两个字符串字面量。
解:
compare<std::string>("hello", "world")
练习16.40
下面的函数是否合法?如果不合法,为什么?如果合法,对可以传递的实参类型有什么限制(如果有的话)?返回类型是什么?
template <typename It>auto fcn3(It beg, It end) -> decltype(*beg + 0){//处理序列return *beg;}
解:
合法。该类型需要支持 + 操作。
练习16.41
编写一个新的
sum版本,它返回类型保证足够大,足以容纳加法结果。
解:
template<typename T>auto sum(T lhs, T rhs) -> decltype( lhs + rhs){return lhs + rhs;}
练习16.42
对下面每个调用,确定
T和val的类型:
template <typename T> void g(T&& val);int i = 0; const int ci = i;(a) g(i);(b) g(ci);(c) g(i * ci);
解:
(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呢?
template <typename T> void g(T&& val) { vector<T> v; }
解:
当使用字面常量,T将为int。
当使用int变量,T将为int&。编译的时候将会报错,因为没有办法对这种类型进行内存分配,无法创建vector<int&>。
练习16.46
解释下面的循环,它来自13.5节中的
StrVec::reallocate:
for (size_t i = 0; i != size(); ++i)alloc.construct(dest++, std::move(*elem++));
解:
在每个循环中,对 elem 的解引用操作 * 当中,会返回一个左值,std::move 函数将该左值转换为右值,提供给 construct 函数。
练习16.47
编写你自己版本的翻转函数,通过调用接受左值和右值引用参数的函数来测试它。
解:
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函数。
解:
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);elseret << " null pointer";return ret.str();}
练习16.49
解释下面每个调用会发生什么:
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);
解:
g(42); //g(T )g(p); //g(T*)g(ci); //g(T)g(p2); //g(T*)f(42); //f(T)f(p); //f(T)f(ci); //f(T)f(p2); //f(const T*)
练习16.50
定义上一个练习中的函数,令它们打印一条身份信息。运行该练习中的代码。如果函数调用的行为与你预期不符,确定你理解了原因。
解:
略
练习16.51
调用本节中的每个
foo,确定sizeof...(Args)和sizeof...(rest)分别返回什么。
解:
#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;}
结果:
sizeof...(Args): 3sizeof...(rest): 3sizeof...(Args): 2sizeof...(rest): 2sizeof...(Args): 1sizeof...(rest): 1sizeof...(Args): 0sizeof...(rest): 0
练习16.52
编写一个程序验证上一题的答案。
解:
参考16.51。
练习16.53
编写你自己版本的
解:
template<typename Printable>std::ostream& print(std::ostream& os, Printable const& printable){return os << printable;}// recursiontemplate<typename Printable, typename... Args>std::ostream& print(std::ostream& os, Printable const& printable, Args const&... rest){return print(os << printable << ", ", rest...);}
练习16.54
如果我们对一个没
<<运算符的类型调用
解:
无法通过编译。
练习16.55
如果我们的可变参数版本
解:
error: no matching function for call to 'print(std::ostream&)'
练习16.56
编写并测试可变参数版本的
errorMsg。
解:
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函数。
解:
template<typename T> //for the class templatetemplate<typename... Args> //for the member templateinline voidVec<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。
解:
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。
解:
#include <iostream>#include <vector>#include <cstring>// templatetemplate<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 specializationtemplate<>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(){// for ex16.63std::vector<double> vd = { 1.1, 1.1, 2.3, 4 };std::cout << count(vd, 1.1) << std::endl;// for ex16.64std::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 *参数。将这两个函数重写为特例化版本。
解:
#include <iostream>#include <vector>#include <cstring>#include <sstream>// templatetemplate <typename T>std::string debug_rep(T* t);// template specialization T=const char* , char* respectively.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 specialization// T = const char*template<>std::string debug_rep(const char* str){std::string ret(str);return str;}// template specialization// T = char*template<>std::string debug_rep( char *str){std::string ret(str);return ret;}
练习16.66
重载
debug_rep函数与特例化它相比,有何优点和缺点?
解:
重载函数会改变函数匹配。
练习16.67
定义特例化版本会影响
debug_rep的函数匹配吗?如果不影响,为什么?
解:
不影响,特例化是模板的一个实例,并没有重载函数。
