《C++ Primer》第16章 模板与泛型编程
16.4节可变参数模板 习题答案
练习16.51:调用本节中的每个foo,确定sizeof…(Args)和sizeof…(rest)分别返回什么。
【出题思路】
理解可变参数模板。
【解答】
对4个调用,sizeof…(Args)和sizeof…(rest)分别返回:
3 3
2 2
1 1
0 0
练习16.52:编写一个程序验证你对上一题的答案。
【出题思路】
理解可变参数模板。
【解答】
#include <iostream>
#include <string>
using std::cout;
using std::endl;
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 = "hello world";
foo(i, s, 42, d); //包中有三个参数
foo(s, 42, "hi"); //包中有两个参数
foo(d,s); //包中有一个参数
foo("hi"); //空包
return 0;
}
运行结果:
练习16.53:编写你自己版本的print函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应有不同的类型。
【出题思路】
参考书中本节内容编写即可。
【解答】
#include <iostream>
#include <string>
using namespace std;
//用来终止递归并打印最后一个元素的函数
//此函数必须在可变参数版本的print定义之前声明
template <typename T>
ostream &print(ostream &os, const T &t)
{
return os << t << endl;//包中最后一个元素之后不打印分隔符
}
//包中除了最后一个元素之外的其他元素都会调用这个版本的print
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)//扩展Args
{
os << t << ", ";//打印第一个实参
return print(os, rest...);//扩展rest,递归打印其他参数
}
int main()
{
int i = 0;
string s = "Hello";
print(cout, i);
print(cout, i, s);
print(cout, i, s, 42.1, 'A', "End");
return 0;
}
运行结果:
练习16.54:如果我们对一个没有<<运算符的类型调用print,会发生什么?
【出题思路】
理解模板对类型参数的要求。
【解答】
由于print要求函数参数类型支持<<运算符,因此会产生编译错误。
练习16.55:如果我们的可变参数版本print的定义之后声明非可变参数版本,解释可变参数的版本会如何执行。
【出题思路】
理解模板对类型参数的要求。
【解答】
将非可变参数版本放在可变参数版本之后,也属于“定义可变参数版本时,非可变参数版本声明不在作用域中”的情况。因此,可变参数版本将陷入无限递归。注意,这里的无限递归并不是运行时的无限递归调用,而是发生在编译时递归的包扩展。例如,调用print(cout, i, s, 42),正常的包扩展过程是:
print(cout, s, 42);
print(cout, 42);
最后一步与非可变参数版本匹配。但当非可变参数版本不在作用域中时,还会继续扩展为:
print(cout);
这就无法与任何模板匹配了,从而产生编译错误。
练习16.56:编写并测试可变参数版本的errorMsg。
【出题思路】
理解并练习包扩展。
【解答】
参考书中本节内容编写即可。需要注意的是,print要求参数类型支持<<运算符,因此需为Sales_data定义<<运算符:
ostream &operator<<(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
练习16.57:比较你的可变参数版本的errorMsg和6.2.6节(第198页)中的error_msg函数。两种方法的优点和缺点各是什么?
【出题思路】
理解可变参数模板。
【解答】
相对于6.2.6节的版本,本节的版本不要求参数具有相同类型。当不知道实参数目也不知道它们的类型时,本节的版本非常有用。而6.2.6节的版本只能适用于相同类型的多个参数的情形,对另一种类型,就要为其编写新的版本,编程工作量大。当然,本节的版本较为复杂,需要更多模板相关的知识来确保代码正确,例如复杂的扩展模式。
练习16.58:为你的StrVec类及你为16.1.2节(第591页)中练习编写的Vec类添加emplace_back函数。
【出题思路】
本题练习转发参数包。
【解答】
参照书中本节内容编写即可。为主程序添加如下测试代码:
cout << "emplace " << svec.size() << endl;
svec.emplace_back("End");
svec.emplace_back(3, '!');
print(svec);
练习16.59:假定s是一个string,解释调用svec.emplace_back(s)会发生什么。
【出题思路】
本题要求理解参数包转发过程。
【解答】
由于s是一个左值,经过包扩展,它将以如下形式传递给construct。
std::forward<string>(s);
forward<string>的结果类型是string&,因此,construct将得到一个左值引用实参,它继续将此参数传递给string的拷贝构造函数来创建新元素。
练习16.60:解释make_shared(参见12.1.1节,第401页)是如何工作的。
【出题思路】
本题要求理解参数包转发过程。
【解答】
make_shared的工作过程类似emplace_back。它接受参数包,经过扩展,转发给new,作为vector的初始化参数。
练习16.61:定义你自己版本的make_shared。
【出题思路】
本题练习定义参数包转发。
【解答】
参考emplace_back的设计,可以很容易地编写出make_shared:
template <typename T, class... Args>
SP<T> make_SP(Args&&... args)
{
return SP<T>(new T(std::forward<Args>(args)...));
}
在练习16.30的主程序的基础上增加如下测试代码即可:
//测试make_SP
vector<string> vs = {"Hello", "World", "!"};
Blob<string> b3(vs.begin(), vs.end());
cout << b3.size() << endl;
for(size_t i = 0; i < b3.size(); ++i)
cout << b3.at(i) << " ";
cout << endl << endl;
string as[3] = {"This", "is", "end"};
Blob<string> b4(as, as + 3);
cout << b4.size() << endl;
for(size_t i = 0; i < b4.size(); ++i)
cout << b4.at(i) << " ";
cout << endl << endl;