本文答案,部分参考于C++ Primer 习题集
前面章节的习题答案
配套的学习资料
https://www.jianguoyun.com/p/DTK5uJgQldv8CBjKv80D
16.1
当调用一个函数模板时,编译器会利用给定的函数实参来推断模板实参,用此实际实参代替模板参数来创建出模板的一个新的"实例",也就是一个真正可以调用的函数,这个过程称为实例化.
16.2
/*
* This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
* Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
* copyright and warranty notices given in that book:
*
* "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
*
*
* "The authors and publisher have taken care in the preparation of this book,
* but make no expressed or implied warranty of any kind and assume no
* responsibility for errors or omissions. No liability is assumed for
* incidental or consequential damages in connection with or arising out of the
* use of the information or programs contained herein."
*
* Permission is granted for this code to be used for educational purposes in
* association with the book, given proper citation if and when posted or
* reproduced.Any commercial use of this code requires the explicit written
* permission of the publisher, Addison-Wesley Professional, a division of
* Pearson Education, Inc. Send your request for permission, stating clearly
* what code you would like to use, and in what specific way, to the following
* address:
*
* Pearson Education, Inc.
* Rights and Permissions Department
* One Lake Street
* Upper Saddle River, NJ 07458
* Fax: (201) 236-3290
*/
#ifndef COMPARE_H
#define COMPARE_H
#include <cstring>
// implement strcmp-like generic compare function
// returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller
template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
// special version of compare to handle C-style character strings
template <>
inline
int compare(const char* const &v1, const char* const &v2)
{
return std::strcmp(v1, v2);
}
#endif
16.3
在tdm gcc-4.8.1中,对两个Sales_data对象调用compare函数模板,编译器会报告如下错误,原因是compare是用<运算符来比较两个对象的.需要类型T事先定义<运算符,但Sales_data类并未定义<运算符,因此会报告错误.
16.4
16.5
16.6
16.7
16.8
泛型编译的一个目的就是令算法是"通用的"------适用于不同类型,所有标准库容器都定义了==和!=运算符,但其中只有少数定义了<运算符,因此,尽量使用!=而不是<,可减少你的算法适用容器的限制.
16.9
简单来说,函数模板是可以实例化出特定函数的模板,类模板是可以实例化出特定类的模板,从形式上来说,函数模板于普通函数相似,只要以关键字template开始,后接模板参数列表,类模板与普通类的关系类似,在使用上,编译器会根据调用为我们推断函数模板的模板参数类型.而是用类模板实例化特定类就必须显示指定模板参数.
16.10
当我们使用一个类模板时,必须显示提供模板实参列表,编译器将它们绑定到模板参数,来替换模板定义中模板参数出现的地方.这样,就实例化出一个特定的类.我们随后使用的其实也是这个特定的类.
16.11
16.12
/*
* This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
* Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
* copyright and warranty notices given in that book:
*
* "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
*
*
* "The authors and publisher have taken care in the preparation of this book,
* but make no expressed or implied warranty of any kind and assume no
* responsibility for errors or omissions. No liability is assumed for
* incidental or consequential damages in connection with or arising out of the
* use of the information or programs contained herein."
*
* Permission is granted for this code to be used for educational purposes in
* association with the book, given proper citation if and when posted or
* reproduced.Any commercial use of this code requires the explicit written
* permission of the publisher, Addison-Wesley Professional, a division of
* Pearson Education, Inc. Send your request for permission, stating clearly
* what code you would like to use, and in what specific way, to the following
* address:
*
* Pearson Education, Inc.
* Rights and Permissions Department
* One Lake Street
* Upper Saddle River, NJ 07458
* Fax: (201) 236-3290
*/
#include "Version_test.h"
#ifndef BLOB_H
#define BLOB_H
#include <iterator>
#include <string>
#include <vector>
#ifdef INITIALIZER_LIST
#include <initializer_list>
#endif
#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();
#ifdef INITIALIZER_LIST
Blob(std::initializer_list<T> il);
// if no initializer_list support use the iterator constructor
#endif
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()
{ auto 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 push_back(T &&t) { data->push_back(std::move(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(std::make_shared<std::vector<T>>(p, p + n)) { }
template <typename T>
Blob<T>::Blob():
data(std::make_shared<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(std::make_shared<std::vector<T>>(b, e)) { }
#ifdef INITIALIZER_LIST
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il):
data(std::make_shared<std::vector<T>>(il)) { }
#endif
// 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 : public std::iterator<std::bidirectional_iterator_tag,T> {
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)
{ auto 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
{ auto p = check(i, "subscript out of range");
return (*p)[i]; // (*p) is the vector to which this object points
}
T& operator*() const
{ auto 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
{
auto 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
16.13
由于函数模板的实例化只处理特定类型,因此,对于相等和关系运算符,对每个BlobPtr实例与用相同类型实例化的运算符建立1对1的友好关系即可.
template<typname T> class BlobPtr{
friend bool;
operator==<T>(const BlobPtr<T> &,const BlobPtr<T> &);
}
16.14
16.15
16.16
类模板有一个类型参数T,表示向量的元素类型,在模板定义中,将原来的string用T替换即可.类模板编写方式与前几题类似
16.17
16.18
a 非法,必须指出U是类型参数(用typename)还是非类型参数
b 非法,在作用域中,模板参数不能重用,而这里重用T作为函数参数名.
c 非法,在模板定义时才能指定inline
d 非法,未指定函数模板返回类型
e 合法,在模板作用域中,类型参数Ctype屏蔽了之前定义的类型别名Ctype
16.19
16.20
16.21
/*
* This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
* Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
* copyright and warranty notices given in that book:
*
* "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
*
*
* "The authors and publisher have taken care in the preparation of this book,
* but make no expressed or implied warranty of any kind and assume no
* responsibility for errors or omissions. No liability is assumed for
* incidental or consequential damages in connection with or arising out of the
* use of the information or programs contained herein."
*
* Permission is granted for this code to be used for educational purposes in
* association with the book, given proper citation if and when posted or
* reproduced.Any commercial use of this code requires the explicit written
* permission of the publisher, Addison-Wesley Professional, a division of
* Pearson Education, Inc. Send your request for permission, stating clearly
* what code you would like to use, and in what specific way, to the following
* address:
*
* Pearson Education, Inc.
* Rights and Permissions Department
* One Lake Street
* Upper Saddle River, NJ 07458
* Fax: (201) 236-3290
*/
#ifndef DEBUGDELETE_H
#define DEBUGDELETE_H
#include <cstddef>
#include <iostream>
#include <string>
// function-object class that calls delete on a given pointer
class DebugDelete {
public:
DebugDelete(const std::string &s = "unique_ptr",
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
16.22
只需对TextQuery.cpp中file的初始化进行修改.创建一个DebugDelete对象作为第二个参数即可.
TextQuery::TextQuery(ifstream &is):file(new vector<string>,DebugDelete("shared_ptr"));
16.23
当shared_ptr的引用计数变为0,需要释放资源时,才会调用删除器进行资源释放.分析查询主程序,runQueries函数结束时,TextQuery对象tq生命周期结束,此时调用运算符呗执行,释放资源,打印一条信息,由于runQueries是主函数最后执行的语句,因此运行效果是程序结束前最后打印出信息.
编译运行上一题的程序,观察输出结果是否如此
16.24
Blob<string> b3(b1.begin(),b1.end());
for(auto p=b3.begin();p!=b3.end();++p)
cout<<*p<<endl;
16.25
第一条语句的extern表明不在本文件中生成实例化代码.该实例化的定义会在程序的其他文件中.
第二条语句用Sales_data实例化vector,在其他文件中可以extern声明此实例化.使用此定义.
16.26
否定
原因是,当我们显示实例化vector时,编译器会实例化vector的所有成员函数,包括它接受容器大小参数的构造函数,vector的这个构造函数会使用元素类型的默认构造函数来对元素类型的默认构造函数进行值初始化,而NoDefault没有默认构造函数,从而导致编译错误.
16.27
a,b,c,f都分别发生了Stack对char,double,int和string的实例化.因为这些语句都要用到这些实例化的类.
d.e未发生实例化.因为在本文件之前的位置已经发生了所需的实例化.
16.28
16.29
对Blob类模板,需将shared_ptr替换成SP,将make_shared替换未make_SP,可以看出,我们并未完整实现shared_ptr和unique_ptr的全部功能,如,我们并未完整实现shared_ptr和unique_ptr的全部功能.如,为实现->运算符,因此,在Blob类模板中,我们将使用->运算符的地方都改为先使用解引用运算符*,再使用.运算符,同时可以看到,我们并没有将所有->改为 * 和. ,这是由于类模板的特性,未使用的成员函数是不实例化的.因此,主函数中未;使用到的地方不进行修改.程序也能正确编译运行.读者可尝试实现shared_ptr和unique_ptr的完整功能.并把Blob中未修改的地方也修改过来.
Blob中的修改也比较零散,这里不再列出.
16.30
16.31
shared_ptr是运行时绑定删除其,而unique_ptr则是编译时绑定删除器.unique_ptr有两个模板参数,一个是所管理的对象类型.另一个是删除器类型.因此,删除器类型是unique_ptr类型的一部分,在编译时就可知道.删除器可直接保存在unique_ptr对象中.通过这种方式,unique_ptr避免了间接调用删除器的运行时的开销.而编译时还可以将自定义的删除器,如DebugDelete编译为内联形式.
16.32
对一个函数模板,当我们调用它时,编译器会利用调用中的函数实参来推断器模板参数,这些模板实参实例化出的版本与我们的函数调用应该是最匹配的版本.这个过程就称为模板实参推断.
16.33
1 const转换:可以将一个会const对象的引用(或指针)传递给一个const对象(指针)的形参
2 数组或函数到指针的转换,如果函数形参不是引用类型,则可以对数组或函数类型的实参引用正常的指针转换.一个数组实参可以转换为一个指向其首元素的指针,
16.34
在模板实参推断过程中,允许数组到指针的转换,但是,如果形参是一个引用.则数组不会转换为一个指针,因此,两个调用都是非法的.
16.35
(a)调用是非法的,因为calc的第二个参数是int类型,所以可以进行正常的类型转换,将char转换成int,而T呗推断为char
(b)调用合法,同(a),对f进行正常的类型转换,将float转换为int,T被推断为double
©调用合法,c和’c’都是char类型,T被推断为char
(d)调用非法,d为double类型,f为float类型,T无法推断为适配的类型.
16.36
(a)调用合法,T被推断为int*
(b)调用合法,T1和T2都被推断为int *
©调用合法,T被推断为const int *
(e)调用非法,T被推断为int *或是const int *,都不能匹配调用
(f)调用合法,T1被推断为int *,T2被推断为const int *
16.37
可以用一个int和一个double调用Max,显示指定模板实参即可.
16.38
在调用make_shared时,有时不给出参数,表示进行值初始化,有时给出的参数与维护的动态对象的类型不相关.如make_shared(10,‘9’)创建值为 "999999999999"的string,因此,编译器无法从参数推断模板类型.就需要显示指定模板实参.
16.39
compare<string>("Hello","World");
16.40
函数是合法的,但用decltype(*beg+0)作为尾置返回类型,导致两个问题:
1.序列元素类型必须支持+运算符
2.*beg+0是右值,因此fun3的返回类型被推断为元素类型的常量引用.
16.41
16.42
(a) T为int &,val的类型为int &,原因是,当实参为一个左值时,编译器推断T为实参的左值引用类型.而非左值类型,而int & &&在引用规则的作用下,被折叠为int &
(b) T为const int &,val的类型为const int &,原因同上一题.
© T为int,val的类型为int &&,原因是,实参是一个右值.
16.43
注意,这里是g(i=ci),而不是g(ici),因此,实参不是一个右值,而是一个左值------------------ i=ci返回的是左值i (你可以尝试打印i和 ici的左值来验证).因此,最终T被推断为int &.val经过引用折叠被确定为int &
16.44
(a)T为int ,val的类型为int
(b)T为int,val的类型为int
©T为int.val的类型为int
当g的函数声明为const T&时,表明可以传递给它任何类型的实参.而T的类型推断结果也不会是const的,因此,三个调用情况如下
(a)T为int,val的类型为const int &
(b)T为int,val的类型为const int &
©T为int,val的类型为cont int &
16.45
(a)对g(42),T被推断为int,val的类型为int &&,因此v是整数的vector.
(b)对g(i),T被推断为int &,val的类型折叠为int &,因此,v被声明为int &的vector.回忆一下.vector是如何保存它的元素的?它管理动态内存空间.在其中保存它的元素.这就需要维护动态内存空间的指针.此指针的类型应该是指向元素类型的指针.但注意,引用不是对象.没有实际地址.因此不能定义指向引用的指针.因此不能定义指向int &的指针.也就不能声明int &的vector,编译失败.
16.46
此循环将elem开始的内存中的string对象移到dest开始的内存中.
每个循环中.调用construct在新内存空间中创建对象.若第二个参数是一个左值.则进行拷贝动作,但在上面的代码中,用std::move将一个左值转换为右值引用.这样.construct会调用string的移动构造函数将数据从旧内存空间移动而不是拷贝到新的内存空间中.避免了不必要的数据拷贝操作.
16.47
16.48
16.49
g(42)匹配模板3,T被推断为int
g§匹配模板4,T被推断为int
g(ci)匹配模板3,T被推断为int
g(p2)匹配模板4,T被推断为const int
f(42)匹配模板1,T被推断为int
f§匹配模板1,T被推断为int*
f(ci)匹配模板1,T被推断为int
f(p2)匹配模板2,T被推断为int.
16.50
16.51
对4个调用,sizeof(Args)和sizeof(rest)分别返回.
3 3
2 2
1 1
0 0
16.52
16.53
16.54
由于print要求函数参数类型支持<<运算符.因此会产生编译错误.
16.55
将非可变参数版本放在可变参数版本之后,也属于"定义可变参数版本时,非可变参数版本声明不在作用域中"的情况.因此,可变参数版本将陷入无限递归.
16.56
16.57
16.58
参照书中本节内容编写即可.为主程序添加如下测试代码.
cout<<"emplace "<<svec.size()<<endl;
svec.emplace_back("End");
svec.emplace_back(3,'!');
print(svec);
16.59
由于s是一个左值,经过包扩展,它将以如下形式传递给construct
std::forward<string> (s);
forward 的结果类型是string &,因此,construct将得到一个左值引用实参,它继续将此参数传递给string的拷贝构造函数来创建新元素.
16.60
make_shared的工作过程类似emplace_back
它接受参数包.经过扩展,转发给new.作为vector的初始化参数.
16.61
16.62
16.63
16.64
当注释掉特例化版本时,最后一个occur调用非匹配通用版本.用==比较char *,从而无法找到相等的C风格字符串.
16.65
16.66
16.67
同16.66