## 练习16.1 > 给出实例化的定义。 解: 当编译器实例化一个模版时,它使用实际的模版参数代替对应的模版参数来创建出模版的一个新“实例”。 ## 练习16.2 > 编写并测试你自己版本的 `compare` 函数。 解: ```cpp template 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` 和一个`list`中查找给定值。 解: ```cpp template Iterator find(Iterator first, Iterator last, const Value& v) { for ( ; first != last && *first != value; ++first); return first; } ``` ## 练习16.5 > 为6.2.4节中的`print`函数编写模版版本,它接受一个数组的引用,能处理任意大小、任意元素类型的数组。 解: ```cpp template void print(const Array& arr) { for (const auto& elem : arr) std::cout << elem << std::endl; } ``` ## 练习16.6 > 你认为接受一个数组实参的标准库函数 `begin` 和 `end` 是如何工作的?定义你自己版本的 `begin` 和 `end`。 解: ```cpp template T* begin(const T (&arr)[N]) { return arr; } template T* end(const T (&arr)[N]) { return arr + N; } ``` ## 练习16.7 > 编写一个 `constexpr` 模版,返回给定数组的大小。 解: ```cpp template constexpr unsigned size(const T (&arr)[N]) { return N; } ``` ## 练习16.8 > 在第97页的“关键概念”中,我们注意到,C++程序员喜欢使用 `!=` 而不喜欢 `<` 。解释这个习惯的原因。 解: 因为大多数类只定义了 `!=` 操作而没有定义 `<` 操作,使用 `!=` 可以降低对要处理的类型的要求。 ## 练习16.9 > 什么是函数模版,什么是类模版? 解: 一个函数模版就是一个公式,可用来生成针对特定类型的函数版本。类模版是用来生成类的蓝图的,与函数模版的不同之处是,编译器不能为类模版推断模版参数类型。如果我们已经多次看到,为了使用类模版,我们必须在模版名后的尖括号中提供额外信息。 ## 练习16.10 > 当一个类模版被实例化时,会发生什么? 解: 一个类模版的每个实例都形成一个独立的类。 ## 练习16.11 > 下面 `List` 的定义是错误的。应如何修改它? ```cpp template class ListItem; template class List { public: List(); List(const List &); List& operator=(const List &); ~List(); void insert(ListItem *ptr, elemType value); private: ListItem *front, *end; }; ``` 解: 模版需要模版参数,应该修改为如下: ```cpp template class ListItem; template class List{ public: List(); List(const List &); List& operator=(const List &); ~List(); void insert(ListItem *ptr, elemType value); private: ListItem *front, *end; }; ``` ## 练习16.12 > 编写你自己版本的 `Blob` 和 `BlobPtr` 模版,包含书中未定义的多个`const`成员。 解: Blob: ```cpp #include #include template class Blob { public: typedef T value_type; typedef typename std::vector::size_type size_type; // constructors Blob(); Blob(std::initializer_list il); // number of elements in the Blob 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(); // element access T& back(); T& operator[](size_type i); const T& back() const; const T& operator [](size_type i) const; private: std::shared_ptr> data; // throw msg if data[i] isn't valid void check(size_type i, const std::string &msg) const; }; // constructors template Blob::Blob() : data(std::make_shared>()) {} template Blob::Blob(std::initializer_list il) : data(std::make_shared>(il)) {} template void Blob::check(size_type i, const std::string &msg) const { if (i >= data->size()) throw std::out_of_range(msg); } template T& Blob::back() { check(0, "back on empty Blob"); return data->back(); } template const T& Blob::back() const { check(0, "back on empty Blob"); return data->back(); } template T& Blob::operator [](size_type i) { // if i is too big, check function will throw, preventing access to a nonexistent element check(i, "subscript out of range"); return (*data)[i]; } template const T& Blob::operator [](size_type i) const { // if i is too big, check function will throw, preventing access to a nonexistent element check(i, "subscript out of range"); return (*data)[i]; } template void Blob::pop_back() { check(0, "pop_back on empty Blob"); data->pop_back(); } ``` BlobPtr: ```cpp #include "Blob.h" #include #include template class BlobPtr; template bool operator ==(const BlobPtr& lhs, const BlobPtr& rhs); template bool operator < (const BlobPtr& lhs, const BlobPtr& rhs); template class BlobPtr { friend bool operator == (const BlobPtr& lhs, const BlobPtr& rhs); friend bool operator < (const BlobPtr& lhs, const BlobPtr& rhs); public: BlobPtr() : curr(0) {} BlobPtr(Blob& a, std::size_t sz = 0) : wptr(a.data), curr(sz) {} T& operator*() const { auto p = check(curr, "dereference past end"); return (*p)[curr]; } // prefix BlobPtr& operator++(); BlobPtr& operator--(); // postfix BlobPtr operator ++(int); BlobPtr operator --(int); private: // returns a shared_ptr to the vector if the check succeeds std::shared_ptr> check(std::size_t, const std::string&) const; std::weak_ptr> wptr; std::size_t curr; }; // prefix ++ template BlobPtr& BlobPtr::operator ++() { // if curr already points past the end of the container, can't increment it check(curr, "increment past end of StrBlob"); ++curr; return *this; } // prefix -- template BlobPtr& BlobPtr::operator --() { --curr; check(curr, "decrement past begin of BlobPtr"); return *this; } // postfix ++ template BlobPtr BlobPtr::operator ++(int) { BlobPtr ret = *this; ++*this; return ret; } // postfix -- template BlobPtr BlobPtr::operator --(int) { BlobPtr ret = *this; --*this; return ret; } template bool operator==(const BlobPtr &lhs, const BlobPtr &rhs) { if (lhs.wptr.lock() != rhs.wptr.lock()) { throw runtime_error("ptrs to different Blobs!"); } return lhs.i == rhs.i; } template bool operator< (const BlobPtr &lhs, const BlobPtr &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 ```cpp #include #include template 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 initializer Screen(char c) :contents(H * W, c) {} char get() const // get the character at the cursor { return contents[cursor]; } // implicitly inline Screen &move(pos r, pos c); // can be made inline later friend std::ostream & operator<< (std::ostream &os, const Screen & c) { unsigned int i, j; for (i = 0; i> (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 inline Screen& Screen::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: ```cpp #include /** * @brief a vector like class */ template class Vec { public: Vec() :element(nullptr), first_free(nullptr), cap(nullptr) {} Vec(std::initializer_list l); Vec(const Vec& v); Vec& operator =(const Vec& rhs); ~Vec(); // memmbers 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: // data members T* element; T* first_free; T* cap; std::allocator alloc; // utillities void reallocate(); void chk_n_alloc() { if (size() == capacity()) reallocate(); } void free(); void wy_alloc_n_move(std::size_t n); std::pair alloc_n_copy(T* b, T* e); }; // copy constructor template Vec::Vec(const Vec &v) { /** * @brief newData is a pair of pointers pointing to newly allocated and copied * from range : [b, e) */ std::pair newData = alloc_n_copy(v.begin(), v.end()); element = newData.first; first_free = cap = newData.second; } // constructor that takes initializer_list template Vec::Vec(std::initializer_list l) { // allocate memory as large as l.size() T* const newData = alloc.allocate(l.size()); // copy elements from l to the address allocated T* p = newData; for (const auto &t : l) alloc.construct(p++, t); // build data structure element = newData; first_free = cap = element + l.size(); } // operator = template Vec& Vec::operator =(const Vec& rhs) { // allocate and copy first to protect against self_assignment std::pair newData = alloc_n_copy(rhs.begin(), rhs.end()); // destroy and deallocate free(); // update data structure element = newData.first; first_free = cap = newData.second; return *this; } // destructor template Vec::~Vec() { free(); } /** * @brief allocate new memeory if nessary and push back the new T * @param t new T */ template void Vec::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 void Vec::reserve(std::size_t n) { // if n too small, just return without doing anything if (n <= capacity()) return; // allocate new memory and move data from old address to the new one wy_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 void Vec::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 void Vec::resize(std::size_t n, const T &t) { if (n < size()) { // destroy the range [element+n, first_free) using destructor for (auto p = element + n; p != first_free;) alloc.destroy(p++); // update first_free to point to the new address first_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 std::pair Vec::alloc_n_copy(T *b, T *e) { // calculate the size needed and allocate space accordingly T* 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 void Vec::free() { // if not nullptr if (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 void Vec::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 one T* dest = newData; T* old = element; for (std::size_t i = 0; i != size(); ++i) alloc.construct(dest++, std::move(*old++)); free(); // update data structure element = 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 void Vec::reallocate() { // calculate the new capacity required std::size_t newCapacity = size() ? 2 * size() : 1; // allocate and move old data to the new space wy_alloc_n_move(newCapacity); } ``` ## 练习16.17 > 声明为 `typename` 的类型参数和声明为 `class` 的类型参数有什么不同(如果有的话)?什么时候必须使用`typename`? 解: 没有什么不同。当我们希望通知编译器一个名字表示类型时,必须使用关键字 `typename`,而不能使用 `class`。 ## 练习16.18 > 解释下面每个函数模版声明并指出它们是否非法。更正你发现的每个错误。 ```cpp (a) template void f1(T, U, V); (b) template T f2(int &T); (c) inline template T foo(T, unsigned int *); (d) template f4(T, T); (e) typedef char Ctype; template Ctype f5(Ctype a); ``` 解: * (a) 非法。应该为 `template void f1(T, U, V);`。 * (b) 非法。应该为 `template T f2(int &t);` * (c) 非法。应该为 `template inline T foo(T, unsigned int*);` * (d) 非法。应该为 `template T f4(T, T);` * (e) 非法。`Ctype` 被隐藏了。 ## 练习16.19 > 编写函数,接受一个容器的引用,打印容器中的元素。使用容器的 `size_type` 和 `size`成员来控制打印元素的循环。 解: ```cpp template void print(const Container& c) { for (typename Container::size_type i = 0; i != c.size(); ++i) std::cout << c[i] << " "; } ``` ## 练习16.20 > 重写上一题的函数,使用`begin` 和 `end` 返回的迭代器来控制循环。 解: ```cpp template void print(const Container& c) { for (auto it = c.begin(); it != c.end(); ++it) std::cout << *it << " "; } ``` ## 练习16.21 > 编写你自己的 `DebugDelete` 版本。 解: DebugDelete ```cpp #include class DebugDelete { public: DebugDelete(std::ostream& s = std::cerr) : os(s) {} template 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` 模版添加一个构造函数,它接受两个迭代器。 解: ```cpp template //for class template //for this member Blob::Blob(It b, It e) : data(std::make_shared>(b, e)) { } ``` ## 练习16.25 > 解释下面这些声明的含义。 ```cpp extern template class vector; template class vector; ``` 解: 前者是模版声明,后者是实例化定义。 ## 练习16.26 > 假设 `NoDefault` 是一个没有默认构造函数的类,我们可以显式实例化 `vector`吗?如果不可以,解释为什么。 解: 不可以。如 ```cpp std::vector vec(10); ``` 会使用 `NoDefault` 的默认构造函数,而 `NoDefault` 没有默认构造函数,因此是不可以的。 ## 练习16.27 > 对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模版被实例化,解释为什么;如果未实例化,解释为什么没有。 > ```cpp template class Stack { }; void f1(Stack); //(a) class Exercise { Stack &rds; //(b) Stack si; //(c) }; int main() { Stack *sc; //(d) f1(*sc); //(e) int iObj = sizeof(Stack); //(f) } ``` 解: (a)、(b)、(c)、(f) 都发生了实例化,(d)、(e) 没有实例化。 ## 练习16.28 > 编写你自己版本的 `shared_ptr` 和 `unique_ptr`。 解: shared_ptr ```cpp #pragma once #include #include "delete.h" namespace cp5 { template class SharedPointer; template auto swap(SharedPointer& lhs, SharedPointer& rhs) { using std::swap; swap(lhs.ptr, rhs.ptr); swap(lhs.ref_count, rhs.ref_count); swap(lhs.deleter, rhs.deleter); } template 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& d) { reset(pointer); deleter = d; } // // Dtor // ~SharedPointer() { decrement_and_destroy(); } private: T* ptr; std::size_t* ref_count; std::function 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: ```cpp #include "debugDelete.h" // forward declarations for friendship template class unique_pointer; template void swap(unique_pointer& lhs, unique_pointer& rhs); /** * @brief std::unique_ptr like class template. */ template class unique_pointer { friend void swap(unique_pointer& lhs, unique_pointer& rhs); public: // preventing copy and assignment unique_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 constructor unique_pointer(unique_pointer&& up) noexcept : ptr(up.ptr) { up.ptr = nullptr; } // move assignment unique_pointer& operator =(unique_pointer&& rhs) noexcept; // std::nullptr_t assignment unique_pointer& operator =(std::nullptr_t n) noexcept; // operator overloaded : * -> bool T& operator *() const { return *ptr; } T* operator ->() const { return &this->operator *(); } operator bool() const { return ptr ? true : false; } // return the underlying pointer T* get() const noexcept{ return ptr; } // swap member using swap friend void swap(unique_pointer &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(); }; // swap template inline void swap(unique_pointer& lhs, unique_pointer& rhs) { using std::swap; swap(lhs.ptr, rhs.ptr); swap(lhs.deleter, rhs.deleter); } // move assignment template inline unique_pointer& unique_pointer::operator =(unique_pointer&& rhs) noexcept { // prevent self-assignment if (this->ptr != rhs.ptr) { deleter(ptr); ptr = nullptr; ::swap(*this, rhs); } return *this; } // std::nullptr_t assignment template inline unique_pointer& unique_pointer::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 inline T* unique_pointer::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` 的类型是什么?如果不合法,为什么? ```cpp template 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` 的类型是什么?如果调用不合法,问题何在? ```cpp template T calc(T, int); tempalte 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 > 进行下面的调用会发生什么: ```cpp template f1(T, T); template 标准库 `max` 函数有两个参数,它返回实参中的较大者。此函数有一个模版类型参数。你能在调用 `max` 时传递给它一个 `int` 和一个 `double` 吗?如果可以,如何做?如果不可以,为什么? 解: 可以。提供显式的模版实参: ```cpp int a = 1; double b = 2; std::max(a, b); ``` ## 练习16.38 > 当我们调用 `make_shared` 时,必须提供一个显示模版实参。解释为什么需要显式模版实参以及它是如果使用的。 解: 如果不显示提供模版实参,那么 `make_shared` 无法推断要分配多大内存空间。 ## 练习16.39 > 对16.1.1节 中的原始版本的 `compare` 函数,使用一个显式模版实参,使得可以向函数传递两个字符串字面量。 解: ```cpp compare("hello", "world") ``` ## 练习16.40 > 下面的函数是否合法?如果不合法,为什么?如果合法,对可以传递的实参类型有什么限制(如果有的话)?返回类型是什么? ```cpp template auto fcn3(It beg, It end) -> decltype(*beg + 0) { //处理序列 return *beg; } ``` 解: 合法。该类型需要支持 `+` 操作。 ## 练习16.41 > 编写一个新的 `sum` 版本,它返回类型保证足够大,足以容纳加法结果。 解: ```cpp template auto sum(T lhs, T rhs) -> decltype( lhs + rhs) { return lhs + rhs; } ``` ## 练习16.42 > 对下面每个调用,确定 `T` 和 `val` 的类型: ```cpp template void g(T&& val); int i = 0; const int ci = i; (a) g(i); (b) g(ci); (c) g(i * ci); ``` 解: ```cpp (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` 呢? ```cpp template void g(T&& val) { vector v; } ``` 解: 当使用字面常量,`T`将为`int`。 当使用`int`变量,`T`将为`int&`。编译的时候将会报错,因为没有办法对这种类型进行内存分配,无法创建`vector`。 ## 练习16.46 > 解释下面的循环,它来自13.5节中的 `StrVec::reallocate`: ```cpp for (size_t i = 0; i != size(); ++i) alloc.construct(dest++, std::move(*elem++)); ``` 解: 在每个循环中,对 `elem` 的解引用操作 `*` 当中,会返回一个左值,`std::move` 函数将该左值转换为右值,提供给 `construct` 函数。 ## 练习16.47 > 编写你自己版本的翻转函数,通过调用接受左值和右值引用参数的函数来测试它。 解: ```cpp template void flip(F f, T1&& t1, T2&& t2) { f(std::forward(t2), std::forward(t1)); } ``` ## 练习16.48 > 编写你自己版本的 `debug_rep` 函数。 解: ```cpp template std::string debug_rep(const T& t) { std::ostringstream ret; ret << t; return ret.str(); } template 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 > 解释下面每个调用会发生什么: ```cpp template void f(T); template void f(const T*); template void g(T); template 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); ``` 解: ```cpp 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)`分别返回什么。 解: ```cpp #include using namespace std; template 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): 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` 函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应有不同的类型。 解: ```cpp template std::ostream& print(std::ostream& os, Printable const& printable) { return os << printable; } // recursion template 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`。 解: ```cpp template 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` 函数。 解: ```cpp template //for the class template template //for the member template inline void Vec::emplace_back(Args&&...args) { chk_n_alloc(); alloc.construct(first_free++, std::forward(args)...); } ``` ## 练习16.59 > 假定 `s` 是一个 `string`,解释调用 `svec.emplace_back(s)`会发生什么。 解: 会在 `construst` 函数中转发扩展包。 ## 练习16.60 > 解释 `make_shared` 是如何工作的。 解: `make_shared` 是一个可变模版函数,它将参数包转发然后构造一个对象,再然后一个指向该对象的智能指针。 ## 练习16.61 > 定义你自己版本的 `make_shared`。 解: ```cpp template auto make_shared(Args&&... args) -> std::shared_ptr { return std::shared_ptr(new T(std::forward(args)...)); } ``` ## 练习16.62 > 定义你自己版本的 `hash`, 并定义一个 `Sales_data` 对象的 `unorder_multise`。将多条交易记录保存到容器中,并打印其内容。 解: 略 ## 练习16.63 > 定义一个函数模版,统计一个给定值在一个`vecor`中出现的次数。测试你的函数,分别传递给它一个`double`的`vector`,一个`int`的`vector`以及一个`string`的`vector`。 解: ```cpp #include #include #include // template template std::size_t count(std::vector const& vec, T value) { auto count = 0u; for(auto const& elem : vec) if(value == elem) ++count; return count; } // template specialization template<> std::size_t count (std::vector 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.63 std::vector vd = { 1.1, 1.1, 2.3, 4 }; std::cout << count(vd, 1.1) << std::endl; // for ex16.64 std::vector vcc = { "alan", "alan", "alan", "alan", "moophy" }; std::cout << count(vcc, "alan") << std::endl; return 0; } ``` ## 练习16.64 > 为上一题的模版编写特例化版本来处理`vector`。编写程序使用这个特例化版本。 解: 参考16.64。 ## 练习16.65 > 在16.3节中我们定义了两个重载的 `debug_rep` 版本,一个接受 `const char*` 参数,另一个接受 `char *` 参数。将这两个函数重写为特例化版本。 解: ```cpp #include #include #include #include // template template 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 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` 的函数匹配吗?如果不影响,为什么? 解: 不影响,特例化是模板的一个实例,并没有重载函数。