第十六章 模板和泛型编程 exce
练习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> constexpr
unsigned 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;
// constructors
Blob();
Blob(std::initializer_list<T> 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<std::vector<T>> data;
// throw msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
// constructors
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)
{
// 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<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 element
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:
#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];
}
// 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<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 it
check(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 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<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();
// 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<T> 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<T*, T*> alloc_n_copy(T* b, T* e);
};
// copy constructor
template<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 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<typename T>
Vec<T>& Vec<T>::operator =(const Vec& rhs)
{
// allocate and copy first to protect against self_assignment
std::pair<T*, T*> 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<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 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<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 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<typename T>
std::pair<T*, T*>
Vec<T>::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<typename T>
void Vec<T>::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<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 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<typename T>
void Vec<T>::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
解释下面每个函数模版声明并指出它们是否非法。更正你发现的每个错误。
(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 class
template<typename It> //for this member
Blob<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 friendship
template<typename, typename> class unique_pointer;
template<typename T, typename D> void
swap(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 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<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();
};
// swap
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);
}
// move assignment
template<typename T, typename D>
inline unique_pointer<T, D>&
unique_pointer<T, D>::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<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);
else
ret << " 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): 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
编写你自己版本的
解:
template<typename Printable>
std::ostream& print(std::ostream& os, Printable const& printable)
{
return os << printable;
}
// recursion
template<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 template
template<typename... Args> //for the member template
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
。
解:
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>
// template
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 specialization
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()
{
// for ex16.63
std::vector<double> vd = { 1.1, 1.1, 2.3, 4 };
std::cout << count(vd, 1.1) << std::endl;
// for ex16.64
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 *
参数。将这两个函数重写为特例化版本。
解:
#include <iostream>
#include <vector>
#include <cstring>
#include <sstream>
// template
template <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
的函数匹配吗?如果不影响,为什么?
解:
不影响,特例化是模板的一个实例,并没有重载函数。