16.1
【出题思路】
理解实例化的基本概念。
【解答】
当调用一个函数模版时,编译器会利用给定的函数实参来推断模版实参,用此实际实参代替模版参数来创建出模版的一个新的 ”实例“,也就是一个真正可以调用的函数,这个过程称为实例化。
16.2
【出题思路】
本题练习定义和使用函数模版。
【解答】
代码如下所示:
Compare.h
#ifndef TEST_COMPARE_H
#define TEST_COMPARE_H
#include <cstring>
#include <functional>
template <typename T>
int compare(const T &v1, const T &v2) {
if (std::less<T>()(v1, v2)) return -1;
if (std::less<T>()(v2, v1)) return 1;
return 0;
}
template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]) {
return strcmp(p1, p2);
}
#endif //TEST_COMPARE_H
main.cpp
#include <iostream>
#include "Compare.h"
int main() {
std::cout << compare(1, 0) << std::endl;
std::cout << compare("hi", "mom") << std::endl;
return 0;
}
// 运行结果
1
-5
Process finished with exit code 0
注:-5 是 h
和 m
的 ASCII 码十进制表示的差值(104 - 109)。
16.3
【出题思路】
理解函数模版对参数类型的要求。
【解答】
对两个 Sales_data 对象调用 compare 函数模版,编译器会报告错误。原因是 compare 是用 <
运算符来比较两个对象的,需要类型 T(即这里的 Sales_data 类)事先定义 <
运算符。但 Sales_data 类并未定义 <
运算符,因此会报告错误。
note: candidate template ignored: could not match 'pair<type-parameter-0-0, type-parameter-0-1>' against 'const Sales_data'
operator< (const pair<_T1,_T2>& __x, const pair<_T1,_T2>& __y)
^
1 error generated.
若 Sales_data 类定义了 <
运算符。程序正常执行,如下所示:
Sales_data.h
#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H
#include <string>
#include <iostream>
class Sales_data {
friend std::ostream &operator<<
(std::ostream&, const Sales_data&);
friend std::istream &operator>>(std::istream&, Sales_data&);
friend bool operator==(const Sales_data &, const Sales_data &);
friend std::ostream &print(std::ostream&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
public:
// constructors
Sales_data(): units_sold(0), revenue(0.0) { }
Sales_data(const std::string &s):
bookNo(s), units_sold(0), revenue(0.0) { }
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(std::istream &);
std::string isbn() const { return bookNo; }
Sales_data& operator+=(const Sales_data&);
private:
double avg_price() const;
std::string bookNo;
unsigned units_sold;
double revenue;
};
// non-member Sales_data operations
inline
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{ return lhs.isbn() < rhs.isbn(); }
inline
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
inline
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
return !(lhs == rhs);
}
// old versions
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
// new operator functions
Sales_data operator+(const Sales_data&, const Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
std::istream &operator>>(std::istream&, Sales_data&);
#endif //TEST_SALES_DATA_H
Sales_data.cpp
#include "Sales_data.h"
#include <string>
using std::istream; using std::ostream;
Sales_data::Sales_data(istream &is): units_sold(0), revenue(0.0)
{
is >> *this; // read a transaction from is into this object
}
double Sales_data::avg_price() const
{
if (units_sold)
return revenue/units_sold;
else
return 0;
}
// member binary operator: left-hand operand is bound to the implicit this pointer
// assumes that both objects refer to the same book
Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// assumes that both objects refer to the same book
Sales_data
operator+(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs; // copy data members from lhs into sum
sum += rhs; // add rhs into sum
return sum;
}
istream &operator>>(istream &is, Sales_data &item)
{
double price; // no need to initialize; we'll read into price before we use it
is >> item.bookNo >> item.units_sold >> price;
if (is) // check that the inputs succeeded
item.revenue = item.units_sold * price;
else
item = Sales_data(); // input failed: give the object the default state
return is;
}
ostream &operator<<(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
// operators replace these original named functions
istream &read(istream &is, Sales_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream &print(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs; // copy data members from lhs into sum
sum += rhs; // add rhs into sum
return sum;
}
main.cpp
#include "Sales_data.h"
#include <iostream>
using std::cout; using std::endl; using std::cin;
#include <functional>
using std::less; using std::greater;
template <typename T, typename F>
int compare(const T &v1, const T &v2, F f)
{
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
template <typename T>
int compare(const T &v1, const T &v2)
{
return compare(v1, v2, less<T>());
}
int main()
{
bool i = compare(0, 42); // uses less; i is -1
cout << compare(0, 42) << endl;
// result depends on the isbns in item1 and item2
Sales_data item1(cin), item2(cin);
bool j = compare(item1, item2, compareIsbn);
cout << compare(item1, item2, compareIsbn) << endl;
return 0;
}
// 运行结果
-1
978-7-121-15535-2
978-7-121-15535-3
^D
1
Process finished with exit code 0
Preference:
Sales_data.h 和 Sales_data.cpp 的代码和本书配套网站中 14 的 Sales_data.h 和 Sales_data.cpp 代码相同;main.cpp 的代码和本书配套网站中 16 的 compareDef.cc 的代码相同。
16.4
【出题思路】
本题练习设计函数模版。
【解答】
用模版类型参数 I 表示迭代器类型,用 T 表示值的类型。find 算法接受两个类型为 I 的参数 b、e 表示迭代器,和一个类型为 T 的参数 v 表示要查找的值。函数遍历范围 [b, e)
查找 v,因此对 I 和 T 的要求是 I 必须支持 ++
运算符和 !=
运算符,来实现遍历,并支持 *
运算符来获取元素值,且 *
运算的结果类型必须为 T。当对 vector<int>
调用 find 时,I 被解析为 vector<int>::iterator
,T 被解析为 int
;当对 list<string>
调用 find 时,I 被解析为 list<string>::iterator
,T 被解析为 string
。
代码如下所示:
Find.h
#ifndef TEST_FIND_H
#define TEST_FIND_H
template <typename I, typename T>
I Find(I b, I e, const T &v) {
for ( ; b != e; ++b)
if (*b == v)
return b;
return e;
}
#endif //TEST_FIND_H
main.cpp
#include <iostream>
#include <vector>
#include <list>
#include <string>
#include "Find.h"
int main() {
std::vector<int> ivec{1, 2, 3, 4, 5, 6};
auto iter1 = Find(ivec.begin(), ivec.end(), 3);
if (iter1 == ivec.end())
std::cout << "Can not find 3" << std::endl;
else
std::cout << "Find 3 at position "
<< iter1 - ivec.begin() << std::endl;
std::list<std::string> slis{"c++", "primer", "5th"};
auto iter2 = Find(slis.begin(), slis.end(), "5th");
if (iter2 == slis.end())
std::cout << "Can not find 5th" << std::endl;
else
std::cout << "Find \"5th\"" << std::endl;
return 0;
}
// 运行结果
Find 3 at position 2
Find "5th"
Process finished with exit code 0
16.5
【出题思路】
本题练习设计多模版参数的函数模版。
【解答】
由于希望 print 处理任意大小和任意元素类型的数组,因此需要两个模版参数:T 是类型参数,表示数组元素类型;N 是 size_t 类型常量,表示数组大小。
代码如下所示:
#include <iostream>
#include <string>
template <typename T, size_t N>
void print(const T (&arr)[N]) {
for (const auto &elem : arr)
std::cout << elem;
std::cout << std::endl;
}
int main() {
int a[6] = {0, 2, 4, 6, 8, 10};
std::string s[3] = {"c++", "primer", "5th"};
print(a);
print(s);
return 0;
}
// 运行结果
0246810
c++primer5th
Process finished with exit code 0
16.6
【出题思路】
本题练习设计 begin 和 end。
【解答】
begin 应返回数组首元素指针,因此是 return &arr[0];
,因为数组名是数组首元素地址,所以也可以写成 return arr;
。end 返回尾后指针,因此在 begin 上加上数组大小 N 即可。完成两个函数的编写后,可利用上一题的程序进行验证。
代码如下所示:
#include <iostream>
#include <string>
template <typename T, size_t N>
void print(const T (&arr)[N]) {
for (const auto &elem : arr)
std::cout << elem;
std::cout << std::endl;
}
template <typename T, size_t N>
const T *begin(const T (&arr)[N]) {
return arr;
}
template <typename T, size_t N>
const T *end(const T (&arr)[N]) {
return arr + N;
}
int main() {
int a[6] = {0, 2, 4, 6, 8, 10};
std::string s[3] = {"c++", "primer", "5th"};
print(a);
print(s);
// test
std::cout << *begin(a) << std::endl;
std::cout << *(end(a) - 1) << std::endl;
std::cout << *begin(s) << std::endl;
std::cout << *(end(s) - 1) << std::endl;
return 0;
}
// 运行结果
0246810
c++primer5th
0
10
c++
5th
Process finished with exit code 0
16.7
【出题思路】
本题练习设计 constexpr 模版。
【解答】
由于数组大小是数组类型的一部分,通过模版参数可以获取,因此在 constexpr 模版中直接返回它即可。
程序如下所示:
#include <iostream>
template <typename T, size_t N>
constexpr int SizeOfArray(const T (&arr)[N]) {
return N;
}
int main() {
int a[] = {0, 2, 4, 6, 8, 10};
std::cout << SizeOfArray(a) << std::endl;
return 0;
}
// 运行结果
6
Process finished with exit code 0
16.8
【出题思路】
理解泛型编程的一个重点:算法对类型要求决定了算法的适用范围。
【解答】
泛型编程的一个目标就是令算法是 “通用的” —— 适用于不同类型。所有标准库容器都定义了 ==
和 !=
运算符,但其中只有少数定义了 <
运算符。因此,尽量使用 !=
而不是 <
,可减少你的算法适用容器的限制。
16.9
【出题思路】
理解模版的基本概念。
【解答】
简单来说,函数模版是可以实例化出特定函数的模版,类模版是可以实例化出特定类的模版。从形式上来说,函数模版与普通函数相似,只是要关键字 template 开始,后接模版参数列表;类模版与普通类的关系类似。在使用上,编译器会根据调用来为我们推断函数模版的模版参数类型;而使用类模版实例化特定类就必须显式指定模版参数。
16.10
【出题思路】
理解类模版的实例化过程。
【解答】
当我们使用一个类模版时,必须显式提供模版实参列表,编译器将它们绑定到模版参数,来替换类模版定义中模版参数出现的地方,这样,就实例化出一个特定的类。我们随后使用的其实是这个特定的类。
16.11
【出题思路】
理解类模版不是一个类型。
【解答】
我们应该牢记,类模版的名字不是一个类型名。类模版只有实例化后才能形成类型,而实例化总是要提供模版实参的。因此,在题干代码中直接使用 ListItem
是错误的,应该使用 ListItem<elemType>
,这才是一个类型。
这个规则有一个例外,就是在类模版作用域内,可以不提供实参,直接使用模版名。也就是说,题干代码中,类内的 List<elemType>
可简化为 List
。
修正后的代码如下所示:
template <typename elemType> class ListItem;
template <typename elemType> class List {
public:
List();
List(const List &);
List &operator=(const List &);
~List();
void insert(ListItem<elemType> *ptr, elemType value);
private:
ListItem<elemType> *front, *end;
};
16.12
【出题思路】
Blob.h
#ifndef TEST_BLOB_H
#define TEST_BLOB_H
#include <iterator>
#include <string>
#include <vector>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <memory>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <stdexcept>
// forward declarations needed for friend declarations in Blob
template <typename>
class BlobPtr;
template <typename>
class Blob; // needed for parameters in operator==
template <typename T>
bool operator==(const Blob<T> &, const Blob<T> &);
template <typename T>
class Blob {
// each instantiation of Blob grants access to the version of
// BlobPtr and the equality operator instantiated with the same type
friend class BlobPtr<T>;
friend bool operator==<T>
(const Blob<T> &, const Blob<T> &);
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
// constructors
Blob();
template <typename It>
Blob(It b, It e);
Blob(T *, std::size_t);
// return BlobPtr to the first and one past the last elements
BlobPtr<T> begin() { return BlobPtr<T>(*this); }
BlobPtr<T> end() {
BlobPtr<T> ret = BlobPtr<T>(*this, data->size());
return ret;
}
// number of elements in the Blob
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// add and remove elements
void push_back(const T &t) { data->push_back(t); }
void pop_back();
// element access
T &front();
T &back();
T &at(size_type);
const T &back() const;
const T &front() const;
const T &at(size_type) const;
T &operator[](size_type i);
const T &operator[](size_type i) const;
void swap(Blob &b) { data.swap(b.data); }
private:
std::shared_ptr<std::vector<T> > data;
// throws msg if data[i] isn't valid
void check(size_type i, const std::string &msg) const;
};
// constructors
template <typename T>
Blob<T>::Blob(T *p, std::size_t n):
data(new std::vector<T>(p, p + n)) {}
template <typename T>
Blob<T>::Blob():
data(new std::vector<T>()) {}
template <typename T>
// type parameter for the class
template <typename It>
// type parameter for the constructor
Blob<T>::Blob(It b, It e):
data(new std::vector<T>(b, e)) {}
// check member
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);
}
// element access members
template <typename T>
T &Blob<T>::front() {
// if the vector is empty, check will throw
check(0, "front on empty Blob");
return data->front();
}
template <typename T>
T &Blob<T>::back() {
check(0, "back on empty Blob");
return data->back();
}
template <typename T>
void Blob<T>::pop_back() {
check(0, "pop_back on empty Blob");
data->pop_back();
}
template <typename T>
const T &Blob<T>::front() const {
check(0, "front on empty Blob");
return data->front();
}
template <typename T>
const T &Blob<T>::back() const {
check(0, "back on empty Blob");
return data->back();
}
template <typename T>
T &Blob<T>::at(size_type i) {
// if i is too big, check will throw, preventing access to a nonexistent element
check(i, "subscript out of range");
return (*data)[i]; // (*data) is the vector to which this object points
}
template <typename T>
const T &
Blob<T>::at(size_type i) const {
check(i, "subscript out of range");
return (*data)[i];
}
template <typename T>
T &Blob<T>::operator[](size_type i) {
// if i is too big, check 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 {
check(i, "subscript out of range");
return (*data)[i];
}
// operators
template <typename T>
std::ostream &
operator<<(std::ostream &os, const Blob<T> a) {
os << "< ";
for (size_t i = 0; i < a.size(); ++i)
os << a[i] << " ";
os << " >";
return os;
}
template <typename T>
bool
operator==(const Blob<T> lhs, const Blob<T> rhs) {
if (rhs.size() != lhs.size())
return false;
for (size_t i = 0; i < lhs.size(); ++i) {
if (lhs[i] != rhs[i])
return false;
}
return true;
}
// BlobPtr throws an exception on attempts to access a nonexistent element
template <typename T>
bool operator==(const BlobPtr<T> &, const BlobPtr<T> &);
template <typename T>
class BlobPtr {
friend bool
operator==<T>(const BlobPtr<T> &, const BlobPtr<T> &);
public:
BlobPtr() : curr(0) {}
BlobPtr(Blob<T> &a, size_t sz = 0) :
wptr(a.data), curr(sz) {}
T &operator[](std::size_t i) {
std::shared_ptr<std::vector<T> > p =
check(i, "subscript out of range");
return (*p)[i]; // (*p) is the vector to which this object points
}
const T &operator[](std::size_t i) const {
std::shared_ptr<std::vector<T> > p =
check(i, "subscript out of range");
return (*p)[i]; // (*p) is the vector to which this object points
}
T &operator*() const {
std::shared_ptr<std::vector<T> > p =
check(curr, "dereference past end");
return (*p)[curr]; // (*p) is the vector to which this object points
}
T *operator->() const { // delegate the real work to the dereference operator
return &this->operator*();
}
// increment and decrement
BlobPtr &operator++(); // prefix operators
BlobPtr &operator--();
BlobPtr operator++(int); // postfix operators
BlobPtr operator--(int);
private:
// check 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;
// store a weak_ptr, which means the underlying vector might be destroyed
std::weak_ptr<std::vector<T> > wptr;
std::size_t curr; // current position within the array
};
// equality operators
template <typename T>
bool operator==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) {
return lhs.wptr.lock().get() == rhs.wptr.lock().get() &&
lhs.curr == rhs.curr;
}
template <typename T>
bool operator!=(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) {
return !(lhs == rhs);
}
// check member
template <typename T>
std::shared_ptr<std::vector<T> >
BlobPtr<T>::check(std::size_t i, const std::string &msg) const {
std::shared_ptr<std::vector<T> > ret =
wptr.lock(); // is the vector still around?
if (!ret)
throw std::runtime_error("unbound BlobPtr");
if (i >= ret->size())
throw std::out_of_range(msg);
return ret; // otherwise, return a shared_ptr to the vector
}
// member operators
// postfix: increment/decrement the object but return the unchanged value
template <typename T>
BlobPtr<T> BlobPtr<T>::operator++(int) {
// no check needed here; the call to prefix increment will do the check
BlobPtr ret = *this; // save the current value
++*this; // advance one element; prefix ++ checks the increment
return ret; // return the saved state
}
template <typename T>
BlobPtr<T> BlobPtr<T>::operator--(int) {
// no check needed here; the call to prefix decrement will do the check
BlobPtr ret = *this; // save the current value
--*this; // move backward one element; prefix -- checks the decrement
return ret; // return the saved state
}
// prefix: return a reference to the incremented/decremented object
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 BlobPtr");
++curr; // advance the current state
return *this;
}
template <typename T>
BlobPtr<T> &BlobPtr<T>::operator--() {
// if curr is zero, decrementing it will yield an invalid subscript
--curr; // move the current state back one element
check(-1, "decrement past begin of BlobPtr");
return *this;
}
#endif //TEST_BLOB_H
useBlob.cpp
#include <string>
using std::string;
#include <iostream>
using std::cout; using std::endl;
#include "Blob.h"
int main() {
Blob<string> b1; // empty Blob
cout << b1.size() << endl;
{ // new scope
string temp[] = {"a", "an", "the"};
Blob<string> b2(temp, temp + sizeof(temp) / sizeof(*temp));
b1 = b2; // b1 and b2 share the same elements
b2.push_back("about");
cout << b1.size() << " " << b2.size() << endl;
} // b2 is destroyed, but the elements it points to must not be destroyed
cout << b1.size() << endl;
for (BlobPtr<string> p = b1.begin(); p != b1.end(); ++p)
cout << *p << endl;
return 0;
}
// 运行结果
0
4 4
4
a
an
the
about
Process finished with exit code 0
useChcking.cpp
#include "Blob.h"
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <iostream>
using std::cout; using std::endl;
int main() {
vector<int> v1(3, 43), v2(10);
int temp[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Blob<int> a1(v1.begin(), v1.end()),
a2(temp, temp + sizeof(temp) / sizeof(*temp)),
a3(v2.begin(), v2.end());
cout << a1 << "\n\n" << a2 << "\n\n" << a3 << endl;
cout << "\ncopy" << "\n\n";
Blob<int> a5(a1);
cout << a5 << endl;
cout << "\nassignment" << "\n\n";
a1 = a3;
cout << a1 << "\n\n" << a2 << "\n\n" << a3 << endl;
cout << "\nelement assignment" << "\n\n";
a1[0] = 42;
a1[a1.size() - 1] = 15;
cout << a1 << "\n\n" << a3 << endl;
Blob<string> s1;
s1.push_back("hi");
s1.push_back("bye");
s1.push_back("now");
BlobPtr<string> p(s1); // p points to the vector inside s1
*p = "okay"; // assigns to the first element in s1
cout << p->size() << endl; // prints 4, the size of the first element in s1
cout << (*p).size() << endl; // equivalent to p->size()
Blob<string> s2;
s2.push_back("one");
s1.push_back("two");
s1.push_back("three");
// run the string empty function in the first element in s2
if (s2[0].empty())
s2[0] = "empty"; // assign a new value to the first string in s2
cout << a1 << endl;
cout << a2 << endl;
a2.swap(a1);
cout << a1 << endl;
cout << a2 << endl;
return 0;
}
//运行结果
< 43 43 43 >
< 0 1 2 3 4 5 6 7 8 9 >
< 0 0 0 0 0 0 0 0 0 0 >
copy
< 43 43 43 >
assignment
< 0 0 0 0 0 0 0 0 0 0 >
< 0 1 2 3 4 5 6 7 8 9 >
< 0 0 0 0 0 0 0 0 0 0 >
element assignment
< 42 0 0 0 0 0 0 0 0 15 >
< 42 0 0 0 0 0 0 0 0 15 >
4
4
< 42 0 0 0 0 0 0 0 0 15 >
< 0 1 2 3 4 5 6 7 8 9 >
< 0 1 2 3 4 5 6 7 8 9 >
< 42 0 0 0 0 0 0 0 0 15 >
Process finished with exit code 0
16.13
【出题思路】
理解对模版如何设定友好关系。
【解答】
由于函数模版的实例化只处理特定类型,因此,对于相等和关系运算符,对每个 BlobPtr 实例与用相同类型实例化的关系运算符建立一对一的友好关系即可。
template <typename T>
class BlobPtr {
friend bool
operator==<T>(const BlobPtr<T> &, const BlobPtr<T> &);
... ...
};
16.14
【出题思路】
本题练习定义类模版。
【解答】
代码如下所示:
Screen.h
#ifndef TEST_SCREEN_H
#define TEST_SCREEN_H
#include <algorithm>
#include <iostream>
#include <string>
using pos = std::string::size_type;
template <pos, pos>
class Screen;
template <pos H, pos W>
std::istream &operator>>(std::istream &, Screen<H, W> &);
template <pos H, pos W>
std::ostream &operator<<(std::ostream &, const Screen<H, W> &);
template <pos H, pos W>
class Screen {
friend std::istream &operator>><H, W>(std::istream &, Screen<H, W> &);
friend std::ostream &operator<<<H, W>(std::ostream &, const Screen<H, W> &);
public:
Screen() = default;
Screen(char c) : contents(H * W, c) {}
char get() const { return contents[cursor]; }
char get(pos r, pos c) const { return contents[r * W + c]; }
inline Screen &move(pos r, pos c);
inline Screen &set(char ch);
inline Screen &set(pos r, pos c, char ch);
private:
pos cursor = 0;
std::string contents;
};
template <pos H, pos W>
std::istream &operator>>(std::istream &is, Screen<H, W> &s) {
std::string input;
is >> input;
for (char ch : input) s.set(ch);
return is;
}
template <pos H, pos W>
std::ostream &operator<<(std::ostream &os, const Screen<H, W> &s) {
for (pos r = 0; r != H; ++r) {
for (pos c = 0; c != W; ++c) {
os << s.get(r, c);
}
os << std::endl;
}
return os;
}
template <pos H, pos W>
inline Screen<H, W> &Screen<H, W>::move(pos r, pos c) {
cursor = r * W + c;
return *this;
}
template <pos H, pos W>
inline Screen<H, W> &Screen<H, W>::set(char ch) {
contents[cursor++] = ch;
cursor = std::min(cursor, H * W);
return *this;
}
template <pos H, pos W>
inline Screen<H, W> &Screen<H, W>::set(pos r, pos c, char ch) {
contents[r * W + c] = ch;
return *this;
}
#endif //TEST_SCREEN_H
main.cpp
#include "Screen.h"
int main() {
Screen<5, 5> screen('x');
screen.set(2, 2, 'o');
std::cout << screen << std::endl;
std::cout << "please input some characters as you like:";
std::cin >> screen;
std::cout << screen << std::endl;
}
// 运行结果
xxxxx
xxxxx
xxoxx
xxxxx
xxxxx
please input some characters as you like:#
#xxxx
xxxxx
xxoxx
xxxxx
xxxxx
Process finished with exit code 0
16.15
【出题思路】
本题练习定义模版的友元。
【解答】
代码如练习 16.14
same reason with
Blob
.
16.16
【出题思路】
本题练习模版的定义和使用。
【解答】
类模版有一个类型参数 T,表示向量的元素类型。在模版定义中,将原来的 string 用 T 替换即可。类模版编写方式与前几题类似,没有特别需要注意的地方。读者编写完毕后可与配套网站中的代码对照。
代码如下所示:
Vec.h
#ifndef TEST_VEC_H
#define TEST_VEC_H
#include <memory>
// simplified-implementation of memory allocation strategy for a vector-like class
template <typename T>
class Vec {
public:
Vec() : elements(0), first_free(0), cap(0) { }
Vec(const Vec&); // copy constructor
Vec &operator=(const Vec&); // copy assignment
~Vec(); // destructor
// add elements
void push_back(const T&);
// size and capacity
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
// element access
T &operator[](size_t n) { return elements[n]; }
const T &operator[](size_t n) const { return elements[n]; }
// iterator interface
T *begin() const { return elements; }
T *end() const { return first_free; }
private:
static std::allocator<T> alloc; // allocates the elements
// used by functions that add elements to the Vec
void chk_n_alloc() { if (first_free == cap) reallocate(); }
// utilities used by copy constructor, assignment operator, and destructor
std::pair<T*, T*> alloc_n_copy(const T*, const T*);
void free();
void reallocate(); // get more space and copy existing elements
T* elements; // pointer to first element in the array
T* first_free; // pointer to first free element in the array
T* cap; // pointer to one past the end of the array
};
// definition for the static data member
template <typename T> std::allocator<T> Vec<T>::alloc;
template <typename T>
inline
Vec<T>::~Vec() { free(); }
template <typename T>
inline
std::pair<T*, T*> Vec<T>::alloc_n_copy(const T *b, const T *e) {
T *data = alloc.allocate(e - b);
return std::make_pair(data, uninitialized_copy(b, e, data));
}
template <typename T>
inline
Vec<T>::Vec(const Vec &s) {
// call copy to allocate exactly as many elements as in s
std::pair<T*, T*> newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
template <typename T>
inline
void Vec<T>::free() {
// destroy the old elements in reverse order
for (T *p = first_free; p != elements; /* empty */)
alloc.destroy(--p); // destroy elements in reverse order
// deallocate cannot be called on a 0 pointer
if (elements)
alloc.deallocate(elements, cap - elements);
}
template <typename T>
inline
Vec<T> &Vec<T>::operator=(const Vec &rhs) {
// call copy to allocate exactly as many elements as in rhs
std::pair<T*, T*> data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
template <typename T>
inline
void Vec<T>::reallocate() {
// we'll allocate space for twice as many elements as current size
size_t newcapacity = size() ? 2 * size() : 2;
// allocate new space
T *first = alloc.allocate(newcapacity);
T *dest = first;
T *elem = elements;
// copy the elements
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, *elem++);
free(); // free the old space once we've moved the elements
// update our data structure point to the new elements
elements = first;
first_free = dest;
cap = elements + newcapacity;
}
template <typename T>
inline
void Vec<T>::push_back(const T &s) {
chk_n_alloc(); // reallocates the Vec if necessary
// construct a copy s in the elements to which first_free points
alloc.construct(first_free++, s);
}
#endif //TEST_VEC_H
Vecmain.cpp
#include "Vec.h"
#include <string>
using std::string;
#include <iostream>
using std::cin; using std::cout; using std::endl;
using std::istream;
void print(const Vec<string> &svec) {
for (string *it = svec.begin(); it != svec.end(); ++it)
cout << *it << " ";
cout << endl;
}
Vec<string> getVec(istream &is) {
Vec<string> svec;
string s;
while (is >> s)
svec.push_back(s);
return svec;
}
int main() {
Vec<string> svec = getVec(cin);
print(svec);
cout << "copy " << svec.size() << endl;
Vec<string> svec2 = svec;
print(svec2);
cout << "assign" << endl;
Vec<string> svec3;
svec3 = svec2;
print(svec3);
Vec<string> v1, v2;
Vec<string> getVec(istream &);
v1 = v2; // copy assignment
v2 = getVec(cin); // move assignment
print(v1);
print(v2);
cout << "----- end -----";
return 0;
}
// 运行结果
c++ primer 5th
^D
c++ primer 5th
copy 3
c++ primer 5th
assign
c++ primer 5th
----- end -----
Process finished with exit code 0
16.17
【出题思路】
理解 typename 和 class。
【解答】
当用来声明模版类型参数时, typename 和 class 是完全等价的,都表明模版参数是一个类型。在 C++ 最初引入模版时,是使用 class 的。但为了避免与类(或类模版)定义中的 class 相混淆,引入了 typename 关键字。从字面上看,typename 还暗示了模版类型参数不必是一个类类型。因此,现在更建议使用 typename。
如本节所述,typename 还有其它用途。当在模版类型参数上使用作用域运算符 ::
来访问其成员时,如 T::value_type
,在实例化之前可能无法辨别访问的到底是 static 数据成员还是类型成员。对此,C++ 默认通过 ::
访问的是 static 数据成员。为了指明访问的是类型成员,需要在名字前使用 typename 关键字,如 typename T::value_type()
,表明 value_type 是类型成员,这里是创建一个 value_type 类型的对象,并进行初始化。
16.18
【出题思路】
理解模版参数的作用域等特性。
【解答】
(a)非法。必须指出 U 是类型参数(用 typename)还是非类型参数。
(b)非法。在作用域中,模版参数名不能重用,而这里重用 T 作为函数参数名。
(c)非法。函数模版可以声明为 inline 或 constexpr 的,如同非模版函数一样。inline 或 constexpr 说明符放在模版参数列表之后,返回类型之前。
(d)非法。未定义函数模版返回类型。
(e)合法。在模版作用域中,类型参数 Ctype 屏蔽了之前定义的类型别名 Ctype。
修正:
(a) template <typename T, typename U, typename V> void f1(T, U, V);
// identifier 'U'
(b) template <typename T> T f2(int &);
// typename would be hidden
(c) template <typename T> inline T foo(T, unsigned int*);
// inline must be after template
(d) template <typename T> void f4(T, T);
// return type should be provided
(e) typedef char Ctype;
template <typename T> T f5(Ctype a);
// the typename hides this typedef
16.19
【出题思路】
练习用 typename 指明类型成员。
【解答】
我们设定循环变量的类型为容器类型(模版参数)的 size_type,用容器对象(函数参数)的 size 控制循环的终止条件。在循环体中用 at 来获取容器元素,进行打印。由于 size_type 是容器的类型成员而非静态数据成员,因此在前面加上 typename 特别指出。
代码如下所示:
#include <iostream>
#include <vector>
template <typename Container>
std::ostream &print(const Container &c, std::ostream &os = std::cout) {
using size_type = typename Container::size_type;
for (size_type i = 0; i != c.size(); ++i)
os << c.at(i) << " ";
return os;
}
int main() {
std::vector<int> ivec{1, 2, 3, 4, 5, 6};
print(ivec) << "\n";
return 0;
}
// 运行结果
1 2 3 4 5 6
Process finished with exit code 0
【其它解题思路】
显然,由于使用了 at 获取容器元素,本程序不适用于 list 和 forward_list。当然我们也可以通过迭代器来遍历容器,但这样的话与题意隐含地要求用位置编号控制循环稍微违和。
16.20
【出题思路】
练习定义函数模版,复习用迭代器遍历容器。
【解答】
比上一题更为简单,用 begin 获取容器首位置迭代器,将判定迭代器是否到尾后迭代器(end)作为循环判定条件,在循环中解引用迭代器获取元素值。显然,这种方法的适用范围比上一题的方法更宽,可用于 list 和 forward_list。
代码如下所示:
#include <iostream>
#include <vector>
#include <list>
#include <string>
template <typename Container>
std::ostream &print(const Container &c, std::ostream &os = std::cout) {
for (auto iter = c.begin(); iter != c.end(); ++iter)
os << *iter << " ";
return os;
}
int main() {
std::vector<int> ivec{1, 2, 3, 4, 5, 6};
print(ivec) << "\n";
std::list<std::string> slis{"c++", "primer", "5th"};
print(slis) << "\n";
return 0;
}
// 运行结果
1 2 3 4 5 6
c++ primer 5th
Process finished with exit code 0
16.21
【出题思路】
本题练习定义成员模版。
【解答】
参考书中本节内容编写即可,配套网站上有完整代码供对照。
代码如下所示:
DebugDelete.h
#ifndef TEST_DEBUGDELETE_H
#define TEST_DEBUGDELETE_H
#include <iostream>
#include <string>
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
DebugDelete(const std::string &s, std::ostream &strm = std::cerr)
: os(strm), type(s) { }
// as with any function template, the type of T is deduced by the compiler
template <typename T> void operator()(T *p) const {
os << "deleting " << type << std::endl;
delete p;
}
private:
std::ostream &os; // where to print debugging info
std::string type; // what type of smart pointer we're deleting
};
#endif //TEST_DEBUGDELETE_H
main.cpp
#include "DebugDelete.h"
int main() {
double *p = new double;
DebugDelete d("plain pointer"); // 可像 delete 表达式一样使用的对象
d(p); // 调用 DebugDelete::operator()(double*),释放 p
int *ip = new int;
// 在一个临时 DebugDelete 对象上调用 operator()(int*)
DebugDelete("plain pointer")(ip);
// illustrate other types using DebugDelete as their deleter
std::shared_ptr<int> sp1(new int(42), DebugDelete("shared_ptr"));
return 0;
}
// 运行结果
deleting plain pointer
deleting plain pointer
deleting shared_ptr
Process finished with exit code 0
16.22
【出题思路】
本题练习使用自定义删除器。
【解答】
只需对 练习 12.27 的 TextQuery.cpp 中 file 的初始化进行修改,创建一个 DebugDelete 对象作为第二个参数即可:
// read the input file and build the map of lines to line numbers
TextQuery::TextQuery(ifstream &is) : file(new vector<string>,
DebugDelete("shared_ptr")) { ... ... }
完整 TextQuery.cpp 代码如下:
#include "TextQuery.h"
#include "make_plural.h"
#include "DebugDelete.h"
#include <cstddef>
using std::size_t;
#include <memory>
using std::shared_ptr;
#include <sstream>
using std::istringstream;
#include <string>
using std::string;
using std::getline;
#include <vector>
using std::vector;
#include <map>
using std::map;
#include <set>
using std::set;
#include <iostream>
using std::cerr; using std::cout; using std::cin;
using std::endl; using std::ostream;
#include <fstream>
using std::ifstream;
#include <cctype>
using std::ispunct; using std::tolower;
#include <cstring>
using std::strlen;
#include <utility>
using std::pair;
// because we can't use auto, we'll define typedefs
// to simplify our code
// type of the lookup map in a TextQuery object
typedef map<string, shared_ptr<set<TextQuery::line_no>>> wmType;
typedef wmType::mapped_type lineType;
// the iterator type for the map
typedef wmType::const_iterator wmIter;
// type for the set that holds the line numbers
typedef set<TextQuery::line_no>::const_iterator lineIter;
// read the input file and build the map of lines to line numbers
TextQuery::TextQuery(ifstream &is) : file(new vector<string>,
DebugDelete("shared_ptr")) {
string text;
while (getline(is, text)) { // for each line in the file
file->push_back(text); // remember this line of text
int n = file->size() - 1; // the current line number
istringstream line(text); // separate the line into words
string word;
while (line >> word) { // for each word in that line
word = cleanup_str(word);
// if word isn't already in wm, subscripting adds a new entry
lineType &lines = wm[word]; // lines is a shared_ptr
if (!lines) // that pointer is null the first time we see word
lines.reset(new set<line_no>); // allocate a new set
lines->insert(n); // insert this line number
}
}
}
// not covered in the book -- cleanup_str removes
// punctuation and converts all text to lowercase so that
// the queries operate in a case insensitive manner
string TextQuery::cleanup_str(const string &word) {
string ret;
for (string::const_iterator it = word.begin(); it != word.end(); ++it) {
if (!ispunct(*it))
ret += tolower(*it);
}
return ret;
}
QueryResult TextQuery::query(const string &sought) const {
// we'll return a pointer to this set if we don't find sought
static lineType nodata(new set<line_no>);
// use find and not a subscript to avoid adding words to wm!
// cleanup_str removes punctuation and convert sought to lowercase
wmIter loc = wm.find(cleanup_str(sought));
if (loc == wm.end())
return QueryResult(sought, nodata, file); // not found
else
return QueryResult(sought, loc->second, file);
}
ostream &print(ostream &os, const QueryResult &qr) {
// if the word was found, print the count and all occurrences
os << qr.sought << " occurs " << qr.lines->size() << " "
<< make_plural(qr.lines->size(), "time", "s") << endl;
// print each line in which the word appeared
// for every element in the set
for (lineIter num = qr.lines->begin(); num != qr.lines->end(); ++num)
// don't confound the user with text lines starting at 0
os << "\t(line " << *num + 1 << ")"
<< *(qr.file->begin() + *num) << endl;
return os;
}
// debugging routine, not covered in the book
void TextQuery::display_map() {
wmIter iter = wm.begin(), iter_end = wm.end();
// for each word in the map
for ( ; iter != iter_end; ++iter) {
cout << "word: " << iter->first << "{";
// fetch location vector as a const reference to avoid copying it
lineType text_locs = iter->second;
lineIter loc_iter = text_locs->begin(), loc_iter_end = text_locs->end();
// print all line numbers for this word
while (loc_iter != loc_iter_end) {
cout << *loc_iter;
if (++loc_iter != loc_iter_end)
cout << ", ";
}
cout << "}\n"; // end list of output this word
}
cout << endl; // finished printing entire map
}
其它都不变。在此运行练习 12.27程序:
// 运行结果
enter word to look for, or q to quit: c++
c++ occurs 2 times
(line 1)c++ primer 5th
(line 2)C++ Primer 5th
enter word to look for, or q to quit: q
deleting shared_ptr
Process finished with exit code 0
16.23
【出题思路】
本题旨在理解删除器的工作机制。
【解答】
当 shared_ptr 的引用计数变为 0,需要释放资源时,才会调用删除器进行资源释放。分析查询主程序,runQueries 函数结束时,TextQuery 对象 tq 生命期结束,此时 shared_ptr 的引用计数变为 0,会调用删除器释放资源(string 的 vector),此时调用运算符被执行,释放资源,打印一条信息。由于 runQueries 是主函数最后执行的语句,因此运行效果是程序结束前最后打印出信息。
编译运行上一题的程序,观察输出结果是否如此。
16.24
【出题思路】
本题练习定义类模版的成员模版。
【解答】
参考书中本节内容编写即可。然后修改主程序,添加相应的测试代码,如下:
vector<int> v1(3, 43), v2(10);
int temp[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Blob<int> a1(v1.begin(), v1.end()),
a2(temp, temp + sizeof(temp) / sizeof(*temp)),
a3(v2.begin(), v2.end());
详细代码请参考练习 16.12。
16.25
【出题思路】
理解实例化控制。
【解答】
第一条语句的 extern 表明不在本文件中生成实例化代码,该实例化的定义会在程序的其它文件中。
第二条语句用 Sales_data 实例化 vector 模版类的所有成员,在其它文件中可用 extern 声明此实例化,使用此定义。
16.26
【出题思路】
理解显式实例化类模版会实例化所有成员函数。
【解答】
答案是否定的。
原因是,当我们显式实例化 vector<NoDefault>
时,编译器会实例化 vector 的所有成员函数,包括它接受容器大小参数的构造函数。vector 的这个构造函数会使用元素类型的默认构造函数来对元素进行值初始化,而 NoDefault 没有默认构造函数,从而导致编译错误。
16.27
【出题思路】
理解显式实例化。
【解答】
Solution:
- (a) No instantiation, compiles, it got instantiated when called.
- (b) No instantiation, compiles, references and pointers doesn’t need instantiation
- © Instantiation. Doesn’t compile!
- (d) No instantiation, compiles, references and pointers doesn’t need instantiation
- (e) Instantiation of
Stack<char>
. Doesn’t compile! - (f) Instantiation of
Stack<std::string>
. Doesn’t compile!
Solution from How is a template instantiated? - Stack Overflow
16.28
【出题思路】
本题练习定义复杂的类模版。
【解答】
对于 shared_ptr(我们的版本命名为 SharedPtr),关于引用计数的管理、拷贝构造函数和拷贝赋值运算符等的设计,参考 HasPtr 即可。
对于 unique_ptr(我们的版本命名为 UniquePtr),无需管理引用计数,也不支持拷贝构造函数和拷贝赋值运算符,只需设计 release 和 reset 等函数实现资源释放即可。
unique_ptr 在编译时绑定删除器,shared_ptr 在运行是绑定编译器
代码如下所示:
Deleter.h
#include <iostream>
// function-object class that calls delete on a given pointer
class Deleter {
public:
Deleter(const std::string &s = "smart pointer", std::ostream &strm = std::cerr)
: os(strm), type(s) {}
// as with any function template, the type of T is deduced by the compiler
template<typename T>
void operator()(T *p) const {
os << "deleting " << type << std::endl;
delete p;
}
private:
std::ostream &os; // where to print debugging info
std::string type; // what type of smart pointer we're deleting
};
SharedPtr.h
#ifndef TEST_SHAREDPTR_H
#define TEST_SHAREDPTR_H
template <typename T>
class SharedPtr;
template <typename T>
bool operator==(const SharedPtr<T> &lhs, const SharedPtr<T> &rhs);
template <typename T>
class SharedPtr {
friend bool operator==<T>(const SharedPtr<T> &lhs, const SharedPtr<T> &rhs);
public:
SharedPtr() : ptr(nullptr), cnt(nullptr) {}
SharedPtr(T *p, std::function<void(T *)> d = Deleter())
: ptr(p), del(d), cnt(new std::size_t(1)) {}
// copy-control
SharedPtr(const SharedPtr &p)
: ptr(p.ptr), del(p.del), cnt(p.cnt) {
++*cnt;
debug();
}
// copy assignment
SharedPtr &operator=(const SharedPtr &p);
// member
T operator*() const { return *ptr; }
// arrow
T *operator->() const { return ptr; }
operator bool() const { return ptr != nullptr; }
// reset
void reset(T *p);
void reset(T *p, std::function<void(T *)> deleter);
~SharedPtr();
void debug() const {
if (cnt) {
std::cout << "ref cnt: " << *cnt << std::endl;
} else {
throw std::runtime_error("should not happen");
}
}
private:
T *ptr;
std::function<void(T *)> del;
std::size_t *cnt;
};
template <typename T>
SharedPtr<T> &SharedPtr<T>::operator=(const SharedPtr &p) {
++*p.cnt;
if (--*cnt == 0) {
del ? del(ptr) : delete ptr;
delete cnt;
}
ptr = p.ptr;
del = p.del;
cnt = p.cnt;
debug();
return *this;
}
template <typename T>
void SharedPtr<T>::reset(T *p) {
if (cnt && --*cnt == 0) {
del ? del(ptr) : delete ptr;
delete cnt;
}
ptr = p;
cnt = new std::size_t(1);
}
template <typename T>
void SharedPtr<T>::reset(T *p, std::function<void(T *)> deleter) {
reset(p);
del = deleter;
}
template <typename T>
SharedPtr<T>::~SharedPtr() {
if (--*cnt == 0) {
del ? del(ptr) : delete ptr;
delete cnt;
}
}
// == and != operator
template <typename T>
bool operator==(const SharedPtr<T> &lhs, const SharedPtr<T> &rhs) {
return lhs.ptr == rhs.ptr;
}
template <typename T>
bool operator!=(const SharedPtr<T> &lhs, const SharedPtr<T> &rhs) {
return !(lhs == rhs);
}
// helper function, simulate std::make_shared()
template <typename T>
SharedPtr<T> make_shared() {
SharedPtr<T> s(new T);
return s;
}
#endif //TEST_SHAREDPTR_H
UniquePtr.h
#ifndef TEST_UNIQUEPTR_H
#define TEST_UNIQUEPTR_H
template <typename T, typename D = Deleter>
class UniquePtr {
public:
UniquePtr(T *p = nullptr, D d = Deleter()) : ptr(p), del(d) {}
~UniquePtr() { del(ptr); }
// move ctor
UniquePtr(UniquePtr &&u) : ptr(u.ptr), del(u.del) { u.ptr = nullptr; }
// move assignment
UniquePtr &operator=(UniquePtr &&u);
T operator*() const { return *ptr; }
T *operator->() const { return ptr; }
void reset(T *p) {
del(ptr);
ptr = p;
}
void reset(T *p, D d) {
reset(p);
del = d;
}
private:
T *ptr;
D del;
};
template <typename T, typename D>
UniquePtr<T, D> &UniquePtr<T, D>::operator=(UniquePtr &&u) {
if (this != &u) {
del(ptr);
ptr = u.ptr;
del = u.del;
u.ptr = nullptr;
}
return *this;
}
#endif //TEST_UNIQUEPTR_H
main.cpp
#include "Deleter.h"
#include "SharedPtr.h"
#include "UniquePtr.h"
int main() {
// Deleter 构造函数有默认实参,可传可不传。没有传的话用默认实参
SharedPtr<int> sp1(new int(42), Deleter());
UniquePtr<int> sp2(new int(43), Deleter("unique_ptr"));
return 0;
}
// 运行结果
deleting unique_ptr
deleting smart pointer
Process finished with exit code 0
16.29
【出题思路】
本题练习使用类模版。
【解答】
对 Blob 类模版,需将 std::shared_ptr 替换为 SharedPtr,将 std::make_shared 替换为 make_shared。可以看出,我们并未实现完整的 shared_ptr 和 unique_ptr 的全部功能,由于类的模版的特性,未使用的成员函数是不实例化的。因此,主函数中未用到的地方不进行修改,程序也能正确编译运行。读者可尝试实现 shared_ptr 和 unique_ptr 的完整功能,并把 Blob 中未修改的地方也修改过来。
Blob 中的修改比较零散,这里不再列出,读者阅读源代码即可。
代码如下所示:
-
Deleter.h、UniquePtr.h 同上一题;
-
SharedPtr.h 引入删除器头文件,其它部分同上一题
#include "Deleter.h"
-
Blob.h
#ifndef TEST_BLOB_H #define TEST_BLOB_H #include "SharedPtr.h" #include "UniquePtr.h" #include <iterator> #include <string> #include <vector> #include <cstddef> #include <stdexcept> #include <utility> #include <memory> #include <algorithm> #include <iostream> #include <cstdlib> #include <stdexcept> // forward declarations needed for friend declarations in Blob template <typename> class BlobPtr; template <typename> class Blob; // needed for parameters in operator== template <typename T> bool operator==(const Blob<T> &, const Blob<T> &); template <typename T> class Blob { // each instantiation of Blob grants access to the version of // BlobPtr and the equality operator instantiated with the same type friend class BlobPtr<T>; friend bool operator==<T> (const Blob<T> &, const Blob<T> &); public: typedef T value_type; typedef typename std::vector<T>::size_type size_type; // constructors Blob(); template <typename It> Blob(It b, It e); Blob(T *, std::size_t); // return BlobPtr to the first and one past the last elements BlobPtr<T> begin() { return BlobPtr<T>(*this); } BlobPtr<T> end() { BlobPtr<T> ret = BlobPtr<T>(*this, data->size()); return ret; } // number of elements in the Blob size_type size() const { return data->size(); } bool empty() const { return data->empty(); } // add and remove elements void push_back(const T &t) { data->push_back(t); } void pop_back(); // element access T &front(); T &back(); T &at(size_type); const T &back() const; const T &front() const; const T &at(size_type) const; T &operator[](size_type i); const T &operator[](size_type i) const; void swap(Blob &b) { data.swap(b.data); } private: SharedPtr<std::vector<T> > data; // throws msg if data[i] isn't valid void check(size_type i, const std::string &msg) const; }; // constructors template <typename T> Blob<T>::Blob(T *p, std::size_t n): data(new std::vector<T>(p, p + n)) {} template <typename T> Blob<T>::Blob(): data(new std::vector<T>()) {} template <typename T> // type parameter for the class template <typename It> // type parameter for the constructor Blob<T>::Blob(It b, It e): data(new std::vector<T>(b, e)) {} // check member 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); } // element access members template <typename T> T &Blob<T>::front() { // if the vector is empty, check will throw check(0, "front on empty Blob"); return data->front(); } template <typename T> T &Blob<T>::back() { check(0, "back on empty Blob"); return data->back(); } template <typename T> void Blob<T>::pop_back() { check(0, "pop_back on empty Blob"); data->pop_back(); } template <typename T> const T &Blob<T>::front() const { check(0, "front on empty Blob"); return data->front(); } template <typename T> const T &Blob<T>::back() const { check(0, "back on empty Blob"); return data->back(); } template <typename T> T &Blob<T>::at(size_type i) { // if i is too big, check will throw, preventing access to a nonexistent element check(i, "subscript out of range"); return (*data)[i]; // (*data) is the vector to which this object points } template <typename T> const T & Blob<T>::at(size_type i) const { check(i, "subscript out of range"); return (*data)[i]; } template <typename T> T &Blob<T>::operator[](size_type i) { // if i is too big, check 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 { check(i, "subscript out of range"); return (*data)[i]; } // operators template <typename T> std::ostream & operator<<(std::ostream &os, const Blob<T> a) { os << "< "; for (size_t i = 0; i < a.size(); ++i) os << a[i] << " "; os << " >"; return os; } template <typename T> bool operator==(const Blob<T> lhs, const Blob<T> rhs) { if (rhs.size() != lhs.size()) return false; for (size_t i = 0; i < lhs.size(); ++i) { if (lhs[i] != rhs[i]) return false; } return true; } // BlobPtr throws an exception on attempts to access a nonexistent element template <typename T> bool operator==(const BlobPtr<T> &, const BlobPtr<T> &); template <typename T> class BlobPtr { friend bool operator==<T>(const BlobPtr<T> &, const BlobPtr<T> &); public: BlobPtr() : curr(0) {} BlobPtr(Blob<T> &a, size_t sz = 0) : wptr(a.data), curr(sz) {} T &operator[](std::size_t i) { SharedPtr<std::vector<T> > p = check(i, "subscript out of range"); return (*p)[i]; // (*p) is the vector to which this object points } const T &operator[](std::size_t i) const { SharedPtr<std::vector<T> > p = check(i, "subscript out of range"); return (*p)[i]; // (*p) is the vector to which this object points } T &operator*() const { SharedPtr<std::vector<T> > p = check(curr, "dereference past end"); return (*p)[curr]; // (*p) is the vector to which this object points } T *operator->() const { // delegate the real work to the dereference operator return &this->operator*(); } // increment and decrement BlobPtr &operator++(); // prefix operators BlobPtr &operator--(); BlobPtr operator++(int); // postfix operators BlobPtr operator--(int); private: // check returns a SharedPtr to the vector if the check succeeds SharedPtr<std::vector<T> > check(std::size_t, const std::string &) const; // store a weak_ptr, which means the underlying vector might be destroyed std::weak_ptr<std::vector<T> > wptr; std::size_t curr; // current position within the array }; // equality operators template <typename T> bool operator==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) { return lhs.wptr.lock().get() == rhs.wptr.lock().get() && lhs.curr == rhs.curr; } template <typename T> bool operator!=(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) { return !(lhs == rhs); } // check member template <typename T> SharedPtr<std::vector<T> > BlobPtr<T>::check(std::size_t i, const std::string &msg) const { SharedPtr<std::vector<T> > ret = wptr.lock(); // is the vector still around? if (!ret) throw std::runtime_error("unbound BlobPtr"); if (i >= ret->size()) throw std::out_of_range(msg); return ret; // otherwise, return a SharedPtr to the vector } // member operators // postfix: increment/decrement the object but return the unchanged value template <typename T> BlobPtr<T> BlobPtr<T>::operator++(int) { // no check needed here; the call to prefix increment will do the check BlobPtr ret = *this; // save the current value ++*this; // advance one element; prefix ++ checks the increment return ret; // return the saved state } template <typename T> BlobPtr<T> BlobPtr<T>::operator--(int) { // no check needed here; the call to prefix decrement will do the check BlobPtr ret = *this; // save the current value --*this; // move backward one element; prefix -- checks the decrement return ret; // return the saved state } // prefix: return a reference to the incremented/decremented object 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 BlobPtr"); ++curr; // advance the current state return *this; } template <typename T> BlobPtr<T> &BlobPtr<T>::operator--() { // if curr is zero, decrementing it will yield an invalid subscript --curr; // move the current state back one element check(-1, "decrement past begin of BlobPtr"); return *this; } #endif //TEST_BLOB_H
-
main.cpp
#include <string> using std::string; #include <iostream> using std::cout; using std::endl; #include "Blob.h" int main() { string temp[] = {"a", "an", "the"}; Blob<string> b1(temp, temp + sizeof(temp) / sizeof(*temp)); b1.push_back("about"); cout << b1.size() << endl; for (int i = 0; i < b1.size(); ++i) cout << b1.at(i) << endl; UniquePtr<int> u1(new int (42)); cout << *u1 << endl; return 0; }
// 运行结果 4 a an the about 42 deleting smart pointer deleting smart pointer Process finished with exit code 0
16.30
【出题思路】
本题练习使用类模版。
【解答】
主程序如上一题所示,其中测试了使用 SharedPtr 的 Blob 和 UniquePtr。由于不能与 BlobPtr 一起使用,因此打印 Blob 所有内容时,使用的是 at 获取指定下标元素的方式。
16.31
【出题思路】
理解 shared_ptr 和 unique_ptr 使用删除器的方式。
【解答】
shared_ptr 是运行时绑定删除器,而 unique_ptr 则是编译时绑定删除器。unique_ptr 有两个模版参数,一个是所管理的对象类型,另一个是删除器类型。因此,删除器类型是 unique_ptr 的一部分,在编译时就可知道,删除器可直接保存在 unique_ptr 对象中。通过这种方式,unique_ptr 避免了间接调用删除器的运行时开销,而编译时还可以将自定义的删除器,如 DebugDelete 编译为内联形式(练习16.28 中的 Deleter 删除器也是这个原理)。
16.32
【出题思路】
理解模版实参推断。
【解答】
对一个函数模版,当我们调用它时,编译器会利用调用中的函数实参来推断其模版参数,这些模版实参实例化出的版本与我们的函数调用应该是最匹配的版本,这个过程就称为模版实参推断。
16.33
【出题思路】
理解模版实参推断过程中的类型转换。
【解答】
在模版实参推断过程中,如果函数形参的类型使用了模版类型参数,则只允许进行两种类型转换。
- const 转换:可以将一个非 const 对象的引用(或指针)传递给一个 const 对象(或指针)形参。
- 数组或函数到指针的转化:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针的转化。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。
16.34
【出题思路】
理解模版实参推断过程中的类型转换。
【解答】
在模版实参推断过程中,允许数组到指针的转化。但是,如果形参是一个引用,则数组不会转换为一个指针。因此,两个调用都是非法的。
16.35
【出题思路】
理解有普通类型形参情况下的类型转换。
【解答】
(a)调用合法。因为 calc 的第二个参数是 int 类型,所以可以进行正常的类型转换,将 char 转换为 int。而 T 被推断为 char。
(b)调用合法。同(a),对 f 进行正常的类型转换,将 float 转换为 int,T 被推断为 double。
(c)调用合法。c
和 'c'
都是 char 类型,T 被推断为 char。
(d)调用非法。d 为 double 类型,f 为 float 类型,T 无法推断为适配的类型。
16.36
【出题思路】
理解函数参数类型涉及多模版参数情况下的类型转换。
【解答】
(a) T: int
(b) T1: int, T2: int
(c) T: const int*
(d) T1: const int *, T2: const int *
(e) illegal: error: no matching function for call to 'ff1(int*&, const int*&)'
(f) T1: int*, T2: const int*
16.37
【出题思路】
理解显式指定模版实参。
【解答】
可以用一个 int 和 一个 double 调用 max,显示指定模版实参即可:
auto m = max<double>(1, 2.0);
16.38
【出题思路】
理解显式指定模版实参。
【解答】
Because when we call
make_shared
, it is allowed for no argument. Then, we have nothing to deduce the type of the return type.
16.39
【出题思路】
本题练习显示指定模版实参。
【解答】
代码如下所示:
#include <iostream>
template<typename T>
int compare(const T &v1, const T &v2) {
if (v1 < v2) { return -1; }
if (v2 < v1) { return 1; }
return 0;
}
template<>
int compare(const char *const &v1, const char *const &v2) {
return strcmp(v1, v2);
}
int main() {
// 由于没有指定模板实参,编译器推断出 T 为 char (&)[5] (如
// 果两个字符串字面量长度不同,实例化会出错)
std::cout << compare("hello", "world") << std::endl;
// 显式指定模板类型参数,使用特化版本,字符数组自动转为指
// 针,两个字符串长度不同也不会有问题
std::cout << compare<const char *>("c++", "primer") << std::endl;
return 0;
}
// 运行结果
-1
-13
Process finished with exit code 0
16.40
【出题思路】
熟悉尾置返回类型。
【解答】
函数是合法的,但用 decltype(*beg + 0)
作为尾置返回类型,导致两个问题:
- 序列元素类型必须支持
+
运算。 *beg + 0
是右值,因此 fcn3 的返回类型被推断为元素类型的常量引用。
16.41
【出题思路】
本题练习定义尾置返回类型。
【解答】
令 sum 有两个模版参数,分别表示两个加法运算对象的类型,当然它们应该是相容的类型。在设计尾置返回类型时,首先计算 sum 的两个函数参数的和,然后对它们应用 decltype 来获得足以容纳和的返回类型。
template <typename T1, typename T2>
auto sum(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
16.42
【出题思路】
理解函数模版的函数参数是右值引用时,如何进行类型推断。
对于一个给定类型 X:
X& &
、X& &&
和X&& &
都折叠成类型X&
- 类型
X&& &&
折叠成X&&
【解答】
-
(a) T:
int&
val:int& &&
->int &
T 为
int&
,val 的类型为int&
。原因是,当实参为一个左值时,编译器推断 T 为实参的左值引用类型,而非左值类型。而int& &&
在引用折叠规则的作用下,被折叠为int&
。 -
(b) T:
const int&
val:const int& &&
->const int &
T 为
const int&
,val 的类型为const int&
。原因同上一题。 -
© T:
int
val:int &&
T 为
int
,val 的类型为int&&
。原因是,实参是一个右值,编译器就推断 T 为该右值类型,因此 val 的类型就是右值类型的右值引用。
一个有意思的问题是,对此题,如何验证你的答案?类似书中本节的例子,我们可以在 g 中声明类型为 T 的局部变量 v,将 val 赋值给它。然后打印 v 和 val 的地址,即可判断 T 为 int&
还是 int
。还可通过对 v 赋值来判断 T 是否为 const 的。
16.43
【出题思路】
深入理解类型推断。
【解答】
注意,这里是 g(i = ci)
,而不是 g(i == ci)
。因此,实参不是一个右值,而是一个左值 —— i = ci
返回的是左值 i(你可以尝试打印 i 和 i = ci 的左值来验证)。因此,最终 T 被推断为 int&
,val 经过引用折叠被确定为 int&
。
16.44
【出题思路】
理解函数参数是左值引用时的类型推断。
【解答】
当 g 的函数参数声明为 T
时,表明参数传递是传值的;当 g 的函数参数声明为 const T&
时,正常的绑定规则告诉我们可以传递给 g 任何类型的实参 —— 一个对象(const 或非 const)、一个临时对象或是一个字面值常量。
不管 g 的函数参数的声明为 T
或 const T&
,第一题的三个调用中 T 的类型都是 int
。在 g 的函数参数的声明为 T
时,第一题的三个调用中 val 的类型都是 int
;在 g 的函数参数的声明为 const T&
时,第一题的三个调用中 val 的类型都是 const int&
。
16.45
【出题思路】
深入理解模版参数推断。
【解答】
(a)对 g(42)
,T 被推断为 int
,val 的类型为 int&&
。因此,v 是 int
的 vector。
(b)对 g(i)
,T 被推断为 int&
,val 的类型为 int&
(int& &&
==> int&
)。因此,v 是 int&
的 vector(引用不能作为容器的元素)。
回忆一下,vector 是如何保存它的元素的?它管理动态内存空间,在其中保存它的元素。这就需要维护指向动态内存空间的指针,此指针的类型应该就是容器元素的类型。但注意,引用不是对象,没有实际地址,因此不能定义指向引用的指针(参见 2.3.2 节)。因此,不能定义指向 int&
的指针,也就不能声明 int&
的 vector,编译失败。
16.46
【出题思路】
理解 std::move
的工作原理。
【解答】
此循环将 elem 开始的内存中的 string 对象移动到 dest 开始的内存中。
每个循环步中,调用 construct 在新内存空间中创建对象。若第二个参数是一个左值,则进行拷贝动作。但在上面的代码中,用 std::move
将一个左值转换为右值引用,这样,construct 会调用 string 的移动构造函数将数据从旧内存空间移动而不是拷贝到新的内存空间中,避免了不必要的数据拷贝操作。
16.47
【出题思路】
本题练习转发的设计。
【解答】
参考书中本节内容编写,与配套网站中的代码进行对照即可。完整程序如下所示。读者要特别注意 flip(g, i, 42)
,可尝试改为 flip1
或 flip2
,编译程序,观察现象。
#include <utility>
#include <iostream>
using std::cout; using std::endl;
// 模版接受一个可调用对象和两个参数,将两个参数 "翻转" 后用来调用给定的可调用对象
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2) {
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
void f(int v1, int &v2) { // 注意,v2 是一个引用
cout << v1 << " " << ++v2 << endl;
}
void g(int &&i, int &j) {
cout << i << " " << j << endl;
}
// flip1 实现不完整:顶层 const 和引用都丢掉了
template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2) {
f(t2, t1);
}
template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2) {
f(t2, t1);
}
int main() {
int i = 0, j = 0, k = 0, l = 0;
cout << i << " " << j << " " << k << " " << l << endl;
f(42, i); // f 改变其实参 i
flip1(f, j, 42); // 通过 flip1 调用 f 不会改变 j
flip2(f, k, 42); // 正确:k 被改变了
g(1, i);
flip(g, i, 42); // 正确:第三个参数的右值属性被保留了
cout << i << " " << j << " " << k << " " << l << endl;
return 0;
}
// 运行结果
0 0 0 0
42 1
42 1
42 1
1 1
42 1
1 0 1 0
Process finished with exit code 0
16.48
【出题思路】
理解模版重载。
【解答】
参考书中本节内容编写,与配套网站中的代码进行对照即可。
配套网站中的代码如下所示:
debug_rep.h
#ifndef DEBUG_REP_H
#define DEBUG_REP_H
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
/* this file uses two preprocessor variables to control whether
* we use nontemplate functions or specializations for string
* data and to control whether we define versions of debug_rep
* that handle character pointers.
*
* SPECIALIZED is defined, then we define specializations
* for string, char*, and const char* and do not
* define nontemplate functions taking these types
*
* OVERCHAR only matters if SPECIALIZED is not defined. In this case
* if OVERCHAR is defined, we define one nontemplate function
* that takes a string; if OVERCHAR is not defined
* we also define nontemplate functions taking char* and
* const char*
*/
#ifndef SPECIALIZED
std::string debug_rep(const std::string &s);
#ifndef OVERCHAR
std::string debug_rep(char *p);
std::string debug_rep(const char *cp);
#endif
#endif
// overloaded, not specialized, function templates
template <typename T> std::string debug_rep(const T &t);
template <typename T> std::string debug_rep(T *p);
template <typename T> std::string debug_rep(T b, T e);
template <typename T> std::string debug_rep(const std::vector<T> &v);
#ifdef SPECIALIZED
// specialized versions to handle strings and character pointers
// declarations for specializations should follow declarations for all overloaded templates
template <> std::string debug_rep(const std::string&);
template <> std::string debug_rep(const char*);
template <> std::string debug_rep(char*);
#endif
// print any type we don't otherwise handle
template <typename T> std::string debug_rep(const T &t)
{
#ifdef DEBUG
std::cout << "const T&" << "\t";
#endif
std::ostringstream ret;
ret << t; // uses T's output operator to print a representation of t
return ret.str(); // return a copy of the string to which ret is bound
}
// print pointers as their pointer value
// followed by the object to which the pointer points
// NB: this function will not work properly with char*
template <typename T> std::string debug_rep(T *p)
{
#ifdef DEBUG
std::cout << "T*" << "\t";
#endif
std::ostringstream ret;
ret << "pointer: " << p; // print the pointer's own value
if (p)
ret << " " << debug_rep(*p); // print the value to which p points
else
ret << " null pointer"; // or indicate that the p is null
return ret.str(); // return a copy of the string to which ret is bound
}
#ifndef SPECIALIZED
// print strings inside double quotes
std::string debug_rep(const std::string &s)
#else
template <> std::string debug_rep(const std::string &s)
#endif
{
#ifdef DEBUG
std::cout << "const string &" << "\t";
#endif
return '"' + s + '"';
}
#ifndef OVERCHAR
// convert the character pointers to string and call the string version of debug_rep
std::string debug_rep(char *p)
{
return debug_rep(std::string(p));
}
std::string debug_rep(const char *p)
{
return debug_rep(std::string(p));
}
#endif
#ifdef SPECIALIZED
template<> std::string debug_rep(char *p)
{ return debug_rep(std::string(p)); }
template <> std::string debug_rep(const char *cp)
{ return debug_rep(std::string(cp)); }
#endif
template <typename T> std::string debug_rep(T b, T e)
{
std::ostringstream ret;
for (T it = b; it != e; ++it) {
if (it != b)
ret << ","; // put comma before all but the first element
ret << debug_rep(*it); // print the element
}
return ret.str();
}
template <typename T> std::string debug_rep(const std::vector<T> &v)
{
std::ostringstream ret;
ret << "vector: [";
ret << debug_rep(v.begin(), v.end());
ret << "]";
return ret.str();
}
#endif
main.cpp
#include "debug_rep.h"
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <iostream>
using std::cout; using std::endl;
int main()
{
int temp[] = {1,2,3,4,5,6,7,8,9};
vector<int> v(temp, temp + sizeof(temp)/sizeof(*temp));
string s("hi");
cout << debug_rep(v) << endl;
cout << debug_rep(s) << endl;
cout << debug_rep("hi") << endl;
cout << debug_rep(&v[0]) << endl;
cout << debug_rep(&s) << endl;
const string *sp = &s;
cout << debug_rep(sp) << endl;
char carr[] = "bye"; // calls pointer version if no overloads
cout << debug_rep(carr) << endl;
const char *temp2[] = {"Proust", "Shakespeare", "Barth" } ;
vector<string> authors(temp2, temp2 + sizeof(temp2)/sizeof(*temp2));
cout << debug_rep(authors) << endl;
vector<const char*> authors2(temp2, temp2 + sizeof(temp2)/sizeof(*temp2));
cout << debug_rep(authors2) << endl;
cout << debug_rep(s) << endl;
s += "more stuff";
cout << debug_rep(s) << endl;
s += "\\escape\"and quotes";
cout << debug_rep(s) << endl;
cout << debug_rep("hi world!") << endl; // calls debug_rep(T*)
s = "hi";
const char *cp = "bye";
char arr[] = "world";
cout << debug_rep(s) << endl; // calls specialization debug_rep(const string&
cout << debug_rep(cp) << endl; // calls specialization debug_rep(const char*
cout << debug_rep(arr) << endl;// calls specialization debug_rep(char*
cout << debug_rep(&s) << endl; // calls template debug_rep(T*)
return 0;
}
// 运行结果
vector: [1,2,3,4,5,6,7,8,9]
"hi"
"hi"
pointer: 0x7ffc5bc02a80 1
pointer: 0x7ffee21239e8 "hi"
pointer: 0x7ffee21239e8 "hi"
"bye"
vector: ["Proust","Shakespeare","Barth"]
vector: ["Proust","Shakespeare","Barth"]
"hi"
"himore stuff"
"himore stuff\escape"and quotes"
"hi world!"
"hi"
"bye"
"world"
pointer: 0x7ffee21239e8 "hi"
Process finished with exit code 0
16.49
【出题思路】
理解模版重载。
【解答】
Explain what happens in each of the following calls:
template <typename T> void f(T); //1
template <typename T> void f(const T*); //2
template <typename T> void g(T); //3
template <typename T> void g(T*); //4
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);
Answer:
g(42); // type: int(rvalue) call template 3 T: int instantiation: void g(int)
g(p); // type: int * call template 4 T: int instantiation: void g(int *)
g(ci); // type: const int call template 3 T: const int instantiation: void g(const int)
g(p2); // type: const int * call template 4 T: const int instantiation: void g(const int *)
f(42); // type: int(rvalue) call template 1 T: int instantiation: void f(int)
f(p); // type: int * call template 1 T: int * instantiation: void f(int *)
// f(int *) is an exact match for p(int *) while f(const int *) has a conversion from int * to const int *.
f(ci); // type: const int call template 1 T: const int instantiation: void f(const int)
f(p2); // type: const int * call template 2 T:int instantiation: void f(const int *)
16.50
【出题思路】
理解模版重载。
【解答】
#include <iostream>
using std::cout; using std::endl;
#include <typeinfo>
template<typename T>
void f(T) {
cout << typeid(T).name() << "\ttemplate 1\n";
}
template<typename T>
void f(const T *) {
cout << typeid(T).name() << "\ttemplate 2\n";
}
template<typename T>
void g(T) {
cout << typeid(T).name() << "\ttemplate 3\n";
}
template<typename T>
void g(T *) {
cout << typeid(T).name() << "\ttemplate 4\n";
}
int main() {
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);
}
// 运行结果
i template 3
i template 4
i template 3
i template 4
i template 1
Pi template 1
i template 1
i template 2
Process finished with exit code 0
16.51
【出题思路】
理解可变参数模版。
【解答】
对 5 个调用
foo(i, s, 42, d); // 包中有三个参数
foo(s, 42, "hi"); // 包中有两个参数
foo(d, s); // 包中有一个参数
foo("hi"); // 空包
foo(i, s, s, d); // 包中有三个参数
sizeof...(Args)
和 sizeof...(rest)
分别返回:
3 3
2 2
1 1
0 0
3 3
16.52
【出题思路】
理解可变参数模版。
【解答】
#include <iostream>
using std::cout; using std::endl;
#include <string>
using std::string;
template <typename T, typename ... Args>
void foo(const T &t, const Args& ... rest) {
cout << sizeof...(Args) << " "; // 模版类型参数的数目
cout << sizeof...(rest) << endl; // 函数参数的数目
}
int main() {
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"); // 空包
foo(i, s, s, d); // 包中有三个参数
return 0;
}
// 运行结果
3 3
2 2
1 1
0 0
3 3
Process finished with exit code 0
16.53
【出题思路】
参考书中本节内容编写即可。
【解答】
#include <iostream>
#include <string>
// 用来终止递归并打印最后一个元素的函数
// 此函数必须在可变参数版本的 print 定义之前声明
template <typename T>
std::ostream &print(std::ostream &os, const T &t) {
return os << t << std::endl; // 包中最后一个元素之后不打印分隔符
}
// 包中除了最后一个元素之外的其他元素都会调用这个版本的 print
template <typename T, typename ... Args>
std::ostream &print(std::ostream &os, const T &t, const Args& ... rest) {
os << t << ", "; // 打印第一个实参
return print(os, rest...); // 递归调用,打印其他实参
}
int main() {
int i = 0;
std::string s = "Hello";
print(std::cout, i);
print(std::cout, i, s);
print(std::cout, i, s, 42.1, 'A', "End");
return 0;
}
// 运行结果
0
0, Hello
0, Hello, 42.1, A, End
Process finished with exit code 0
16.54
【出题思路】
理解模版对类型参数的要求。
【解答】
由于 print 要求函数参数类型支持 <<
运算符,因此会产生编译错误。
16.55
【出题思路】
理解模版对类型参数的要求。
【解答】
将非可变参数版本放在可变参数版本之后,也属于 “定义可变参数版本时,非可变参数版本声明不在作用域中” 的情况。因此,可变参数版本将陷入无限递归。
注意,这里的无限递归并不是运行时的无限递归调用,而是发生在编译时递归的包扩展。例如,调用
print(cout, i, s, 42)
正常的包扩展过程是:
print(cout, s, 42)
print(cout, 42) 调用非可变参数版本的 print
最后一步与非可变参数版本匹配。但当非可变参数版本不在作用域中时,还会继续扩展为
print(cout)
这就无法与任何模版匹配了,从而产生编译错误。
16.56
【出题思路】
理解并练习包扩展。
【解答】
debug_rep.h 同练习 15.48
print 函数的可变和非可变参数版本同上一题
代码如下所示:
#include <iostream>
#include <string>
#include <vector>
#include "debug_rep.h"
// 用来终止递归并打印最后一个元素的函数
// 此函数必须在可变参数版本的 print 定义之前声明
template <typename T>
std::ostream &print(std::ostream &os, const T &t) {
return os << t << std::endl; // 包中最后一个元素之后不打印分隔符
}
// 包中除了最后一个元素之外的其他元素都会调用这个版本的 print
template <typename T, typename ... Args>
std::ostream &print(std::ostream &os, const T &t, const Args& ... rest) {
os << t << ", "; // 打印第一个实参
return print(os, rest...); // 递归调用,打印其他实参
}
// 在 print 调用中对每个实参调用 debug_rep
template <typename ... Args>
std::ostream &errorMsg(std::ostream &os, const Args& ... rest) {
// print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an)
return print(os, debug_rep(rest)...);
}
int main() {
int i = 0;
std::string s = "Hello";
errorMsg(std::cout, i);
errorMsg(std::cout, i, s);
errorMsg(std::cout, i, s, 42.1, 'A', "End");
std::vector<std::string> svec{"error: 0", "warning: 0"};
errorMsg(std::cout, svec);
return 0;
}
// 运行结果
0
0, "Hello"
0, "Hello", 42.1, A, "End"
vector: ["error: 0","warning: 0"]
Process finished with exit code 0
16.57
【出题思路】
理解可变参数模版。
【解答】
相对于 6.2.6 节的版本,本节的版本不要求参数具有相同类型。当不知道实参数目也不知道它们的类型时,本节的版本非常有用。而 6.2.6 节的版本只能适用于相同类型的多个参数的情形,对另一种类型,就要为其编写新的版本,编程工作量大。当然,本节的版本较为复杂,需要更多模版相关的知识来确保代码正确,例如复杂的扩展模式。
16.58
【出题思路】
本题练习转发参数包。
相关练习,练习 16.16
【解答】
参考书中本节内容编写即可。为主程序添加相应测试代码即可。
StrVec 类代码如下所示(StrVec.cpp 与 练习 13.39 相同):
StrVec.h
#ifndef TEST_STRVEC_H
#define TEST_STRVEC_H
#include <string>
using std::string;
#include <memory>
using std::allocator;
#include <utility>
using std::pair;
#include <initializer_list>
using std::initializer_list;
// 类 vector 类内存分配策略的简单实现
class StrVec {
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) { }
StrVec(const StrVec&); // 拷贝构造函数
StrVec &operator=(const StrVec&); // 拷贝赋值运算符
~StrVec(); // 析构函数
void push_back(const string&); // 拷贝元素
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
string *begin() const { return elements; }
string *end() const { return first_free; }
// 新加的代码
// reserve 预留一部分空间,需要一个辅助函数 void reallocate(size_t)
void reserve(size_t n) { if (n > capacity()) reallocate(n); }
void resize(size_t);
void resize(size_t, const string &);
// 练习 13.40 —— 新添加的一个构造函数
StrVec(initializer_list<string>);
// 练习 16.58
template <class... Args> void emplace_back(Args &&... args) {
chk_n_alloc(); // 如果需要的话重新分配 Vec 内存空间
alloc.construct(first_free++, std::forward<Args>(args)...);
}
private:
// 由于静态成员需要类外初始化,所以我没有把 alloc 定义为 static
// 因为我不熟悉 allocator<string> 初始化方法
allocator<string> alloc; // 分配元素
// 被添加元素的函数所使用
void chk_n_alloc() {
if (size() == capacity())
reallocate();
}
// 工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
pair<string*, string*> alloc_n_copy(const string*, const string*);
void free(); // 销毁元素并释放内存
void reallocate(); // 获得更多内存并拷贝已有元素
string *elements; // 指向数组首元素的指针
string *first_free; // 指向数组第一个空闲元素的指针
string *cap; // 指向数组尾后位置的指针
// 新加的代码
void reallocate(size_t);
};
#endif //TEST_STRVEC_H
main.cpp
#include "StrVec.h"
#include <iostream>
using std::cout; using std::endl;
int main() {
StrVec svec{"one", "piece"};
cout << "emplace " << svec.size() << endl;
svec.emplace_back("End");
svec.emplace_back(3, '!');
for (string *it = svec.begin(); it != svec.end(); ++it)
cout << *it << " ";
cout << endl;
return 0;
}
// 运行结果
emplace 2
one piece End !!!
Process finished with exit code 0
Vec 类代码如下所示:
Vec.h
#ifndef TEST_VEC_H
#define TEST_VEC_H
#include <memory>
// simplified-implementation of memory allocation strategy for a vector-like class
template <typename T>
class Vec {
public:
Vec() : elements(0), first_free(0), cap(0) { }
Vec(const Vec&); // copy constructor
Vec &operator=(const Vec&); // copy assignment
~Vec(); // destructor
// add elements
void push_back(const T&);
// size and capacity
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
// element access
T &operator[](size_t n) { return elements[n]; }
const T &operator[](size_t n) const { return elements[n]; }
// iterator interface
T *begin() const { return elements; }
T *end() const { return first_free; }
// 练习 16.58
template <class... Args> void emplace_back(Args&&... args) {
chk_n_alloc(); // 如果需要的话重新分配 Vec 内存空间
alloc.construct(first_free++, std::forward<Args>(args)...);
}
private:
static std::allocator<T> alloc; // allocates the elements
// used by functions that add elements to the Vec
void chk_n_alloc() { if (first_free == cap) reallocate(); }
// utilities used by copy constructor, assignment operator, and destructor
std::pair<T*, T*> alloc_n_copy(const T*, const T*);
void free();
void reallocate(); // get more space and copy existing elements
T* elements; // pointer to first element in the array
T* first_free; // pointer to first free element in the array
T* cap; // pointer to one past the end of the array
};
// definition for the static data member
template <typename T> std::allocator<T> Vec<T>::alloc;
template <typename T>
inline
Vec<T>::~Vec() { free(); }
template <typename T>
inline
std::pair<T*, T*> Vec<T>::alloc_n_copy(const T *b, const T *e) {
T *data = alloc.allocate(e - b);
return std::make_pair(data, uninitialized_copy(b, e, data));
}
template <typename T>
inline
Vec<T>::Vec(const Vec &s) {
// call copy to allocate exactly as many elements as in s
std::pair<T*, T*> newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
template <typename T>
inline
void Vec<T>::free() {
// destroy the old elements in reverse order
for (T *p = first_free; p != elements; /* empty */)
alloc.destroy(--p); // destroy elements in reverse order
// deallocate cannot be called on a 0 pointer
if (elements)
alloc.deallocate(elements, cap - elements);
}
template <typename T>
inline
Vec<T> &Vec<T>::operator=(const Vec &rhs) {
// call copy to allocate exactly as many elements as in rhs
std::pair<T*, T*> data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
template <typename T>
inline
void Vec<T>::reallocate() {
// we'll allocate space for twice as many elements as current size
size_t newcapacity = size() ? 2 * size() : 2;
// allocate new space
T *first = alloc.allocate(newcapacity);
T *dest = first;
T *elem = elements;
// copy the elements
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, *elem++);
free(); // free the old space once we've moved the elements
// update our data structure point to the new elements
elements = first;
first_free = dest;
cap = elements + newcapacity;
}
template <typename T>
inline
void Vec<T>::push_back(const T &s) {
chk_n_alloc(); // reallocates the Vec if necessary
// construct a copy s in the elements to which first_free points
alloc.construct(first_free++, s);
}
#endif //TEST_VEC_H
main.cpp
#include "Vec.h"
#include <string>
using std::string;
#include <iostream>
using std::cin; using std::cout; using std::endl;
using std::istream;
void print(const Vec<string> &svec) {
for (string *it = svec.begin(); it != svec.end(); ++it)
cout << *it << " ";
cout << endl;
}
Vec<string> getVec(istream &is) {
Vec<string> svec;
string s;
while (is >> s)
svec.push_back(s);
return svec;
}
int main() {
Vec<string> svec = getVec(cin);
print(svec);
cout << "emplace " << svec.size() << endl;
svec.emplace_back("End");
svec.emplace_back(3, '!');
print(svec);
return 0;
}
// 运行结果
one piece
^D
one piece
emplace 2
one piece End !!!
Process finished with exit code 0
16.59
【出题思路】
本题要求理解参数包转发过程。
【解答】
由于 s 是一个左值,经过包扩展,它将以如下形式传递给 construct。
std::forward<string>(s)
std::forward<string>
的结果类型是 string&
,因此,construct 将得到一个左值引用实参,它继续将此参数传递给 string 的拷贝构造函数来创建新元素。
16.60
【出题思路】
本题要求理解参数包转发过程。
【解答】
make_shared 的工作过程类似 emplace_back。
它接受参数包,经过扩展,转发给 new,作为 vector 的初始化参数。
16.61
【出题思路】
本题练习定义参数包转发。
【解答】
将练习 16.29 中 SharedPtr.h 的
// helper function, simulate std::make_shared()
template <typename T>
SharedPtr<T> make_shared() {
SharedPtr<T> s(new T);
return s;
}
重写为
// helper function, simulate std::make_shared()
template <typename T, class... Args>
SharedPtr<T> make_shared(Args&&... args) {
SharedPtr<T> s(new T(std::forward<Args>(args)...));
return s;
}
即可。
然后,测试即可:
#include <string>
using std::string;
#include <iostream>
using std::cout; using std::endl;
#include "Blob.h"
int main() {
string temp[] = {"a", "an", "the"};
Blob<string> b1(temp, temp + sizeof(temp) / sizeof(*temp));
b1.push_back("about");
cout << b1.size() << endl;
for (int i = 0; i < b1.size(); ++i)
cout << b1.at(i) << endl;
UniquePtr<int> u1(new int (42));
cout << *u1 << endl;
cout << endl << endl;
string as[3] = {"This", "is", "end"};
Blob<string> b2(as, as + 3);
cout << b2.size() << endl;
for (int i = 0; i < b2.size(); ++i)
cout << b2.at(i) << endl;
return 0;
}
// 运行结果
4
a
an
the
about
42
3
This
is
end
deleting smart pointer
deleting smart pointer
deleting smart pointer
Process finished with exit code 0
16.62
【出题思路】
本题练习类模版特例化。
【解答】
参考书中本节内容编写即可,配套网站上有完整代码供参考。
代码如下所示:
Sales_data.h 在练习 16.3 的 Sales_data.h 基础上添加特例化程序即可。如下:
#ifndef TEST_SALES_DATA_H
#define TEST_SALES_DATA_H
#include <string>
#include <iostream>
#include <cstddef> // using std::size_t;
// template <class T> class hash; // 友元声明所需要的
class Sales_data {
friend std::ostream &operator<<
(std::ostream&, const Sales_data&);
friend std::istream &operator>>(std::istream&, Sales_data&);
friend bool operator==(const Sales_data &, const Sales_data &);
friend std::ostream &print(std::ostream&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
friend class std::hash<Sales_data>;
public:
// constructors
Sales_data(): units_sold(0), revenue(0.0) { }
Sales_data(const std::string &s):
bookNo(s), units_sold(0), revenue(0.0) { }
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(std::istream &);
std::string isbn() const { return bookNo; }
Sales_data& operator+=(const Sales_data&);
private:
double avg_price() const;
std::string bookNo;
unsigned units_sold;
double revenue;
};
// non-member Sales_data operations
inline
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{ return lhs.isbn() < rhs.isbn(); }
inline
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
inline
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
return !(lhs == rhs);
}
// old versions
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
// new operator functions
Sales_data operator+(const Sales_data&, const Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
std::istream &operator>>(std::istream&, Sales_data&);
// 打开 std 命名空间,以便特例化 std::hash
namespace std {
template <> // 我们正在定义一个特例化版本,模版参数为 Sales_data
struct hash<Sales_data> {
// 用来散列一个无序容器的类型必须要定义下列类型
typedef std::size_t result_type;
typedef Sales_data argument_type; // 默认情况下,此类型需要 ==
std::size_t operator()(const Sales_data &s) const {
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
// 我们的类使用合成的拷贝控制成员和默认构造函数
};
} // 关闭 std 命名空间;注意:右花括号之后没有分号
#endif //TEST_SALES_DATA_H
Sales_data.cpp 同练习 16.3 一样。测试用主程序如下:
#include "Sales_data.h"
#include <unordered_set>
int main()
{
// 使用 hash<Sales_data> 和 Sales_data 的 == 运算符
std::unordered_multiset<Sales_data> SDset;
Sales_data item;
while (std::cin >> item)
SDset.insert(item);
std::cout << SDset.size() << std::endl;
for (auto sd : SDset)
std::cout << sd << std::endl;
return 0;
}
// 运行结果
// 前三行为输入数据,格式为:ISBN 数量 单价
// 输出格式为:ISBN 数量 总价 平均价格
978-7-121-15535-2 4 99.99
978-7-121-15535-0 5 100
978-7-121-15535-1 2 99.5
^D
3
978-7-121-15535-0 5 500 100
978-7-121-15535-2 4 399.96 99.99
978-7-121-15535-1 2 199 99.5
Process finished with exit code 0
16.63
【出题思路】
本题练习定义函数模版。
【解答】
#include <iostream>
using std::cout; using std::endl;
#include <cstddef>
using std::size_t;
#include <vector>
using std::vector;
#include <cstring> // use strcmp and strcpy
#include <string>
using std::string;
template <typename T>
size_t count(const vector<T> &c, const T &v) {
size_t cnt = 0;
for (auto it = c.begin(); it != c.end(); ++it) {
if (*it == v) {
++cnt;
}
}
return cnt;
}
// 练习 16.64
template <>
size_t count(const vector<char*> &c, char* const& v) {
size_t cnt = 0;
for (auto it = c.begin(); it != c.end(); ++it) {
if (strcmp(*it, v) == 0) {
++cnt;
}
}
return cnt;
}
int main() {
vector<double> dvec = {1.1, 3.14, 2.2, 3.14, 3.3, 4.4};
cout << count(dvec, 3.14) << endl;
vector<int> ivec = {0, 1, 2, 3, 4, 5};
cout << count(ivec, 0) << endl;
vector<string> svec = {"Hello", "World", "!"};
cout << count(svec, string("end")) << endl;
// 练习 16.64
vector<char*> pvec;
pvec.push_back(new char[6]);
pvec.push_back(new char[6]);
pvec.push_back(new char[2]);
strcpy(pvec[0], "Hello");
strcpy(pvec[1], "World");
strcpy(pvec[2], "!");
char *w = new char[6];
strcpy(w, "World");
cout << count(pvec, w) << endl;
delete[] w;
delete pvec[2];
delete pvec[1];
delete pvec[0];
return 0;
}
// 运行结果
2
1
0
1
Process finished with exit code 0
16.64
【出题思路】
本题练习定义函数模版的特例化版本。
另外提醒:我们只能部分特例化类模版,而不能部分特例化函数模版。
【解答】
见上一题。
注意,当注释掉特例化版本时,最后一个 count 调用会匹配通用版本,用 ==
比较(而不是 strcmp) char*
,从而无法找到相等的 C 风格字符串。
16.65
【出题思路】
本题练习定义特例化版本。
【解答】
两个特例化版本如下,完整程序见练习 16.48。
template<> std::string debug_rep(char *p)
{ return debug_rep(std::string(p)); }
template <> std::string debug_rep(const char *cp)
{ return debug_rep(std::string(cp)); }
16.66
【出题思路】
理解特例化与重载的区别。
【解答】
重载是会影响函数匹配的,也就是说,编译器在函数匹配过程中会将新的重载版本作为候选之一来选择最佳匹配。这就需要小心设计,避免实际匹配不如我们所愿。
特例化则不影响函数匹配,它并不是为编译器进行函数匹配提供一个新的选择,而是为模版的一个特殊实例提供不同于原模版的特殊定义,本质上是接管了编译器在完成函数匹配后的部分实例化工作。即,当某个模版是最佳匹配时,且需要实例为这个特殊实例时,不再从原模版进行实例化,而是直接使用这个特殊化版本。因此,特例化更为简单 —— 当一个模版不符合我们的需求时,只需设计满足需求的特例化版本即可。
16.67
【出题思路】
理解特例化。
【解答】
如上题。
评论区