第 16 章
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
- (c) 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&
。原因同上一題。 -
(c) 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
【出題思路】
理解特例化。
【解答】
如上題。