C++ primer 第十二章習題

chapter12 動態內存

練習

12.1.1 節練習

練習12.1

  • 在此代碼的結尾,b1 和 b2 各包含多少個元素?

  •   StrBlob b1;
      {
          StrBlob b2 = {"a", "an", "the"};
          b1 = b2;
          b2.push_back("about");
      }
    

b1原智能指針銷燬,改爲指向b2指向的動態內存。和b2一樣包含4個元素。

練習12.2

  • 編寫你自己的StrBlob 類,包含const 版本的 front 和 back。
#include <string>
#include <vector>
#include <memory>
#include <exception>

class StrBlob {
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> il);
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	// 添加刪減元素
	void push_back(const std::string &t) { data->push_back(t); }
    void pop_back() {check(0, "pop_back on empty StrBlob");
	data->pop_back();}
	// 元素訪問
	std::string& front();
	const std::string& front() const;
	std::string& back();
	const std::string& back() const;
private:
	std::shared_ptr < std::vector<std::string>> data;
	// 如果data[i]不合法,拋出一個異常
	void check(size_type i, const std::string &msg) const;
};

StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }
StrBlob::StrBlob(std::initializer_list<std::string> il) : data(std::make_shared<std::vector<std::string>>(il)) { }

void StrBlob::check(size_type i, const std::string &msg) const {
	if (i >= data->size())
		throw std::out_of_range(msg);
}

std::string& StrBlob::front(){
	// 如果vector爲空, check會拋出一個異常
	check(0, "front on empty StrBolb");
	return data->front();
}

std::string& StrBlob::back() {
	check(0, "back on empty StrBlob");
	return data->back();
}

const std::string& StrBlob::front() const {
	// 如果vector爲空, check會拋出一個異常
	check(0, "front on empty StrBolb");
	return data->front();
}

const std::string& StrBlob::back() const {
	check(0, "back on empty StrBlob");
	return data->back();
}

練習12.3

  • StrBlob 需要const 版本的push_back 和 pop_back嗎?如果需要,添加進去。否則,解釋爲什麼不需要。

可以,但不需要。

先說可以,因爲類的數據成員僅是一個智能指針,因此當使用常量版本時,該指針是固定的,但其指向的內容依然是可以改變的,因此const版本的寫入操作是可以實行的。因此,是可以添加const版本的上述兩個函數的。

而看待該題需從類的使用者角度來考慮。

對於用戶來說,他頂一個一個const版本的類對象,則理應無法對該對象進行寫入操作。因此不需要const版本的寫入函數,以免對使用者帶來多餘的困擾。

豆瓣討論鏈接:https://www.douban.com/group/topic/61573279/

練習12.4

  • 在我們的 check 函數中,沒有檢查 i 是否大於0。爲什麼可以忽略這個檢查?

因爲size_type是無符號類型,一定大於等於0。

練習12.5

  • 我們未編寫接受一個 initializer_list explicit 參數的構造函數。討論這個設計策略的優點和缺點。

優點:可以進行隱式轉換,使用時更靈活。

缺點:可能會讓使用者誤以爲可以數據成員的類型比實際上的多。隱式轉換將會多消耗一部分資源。

12.1.2 節練習

練習12.6

  • 編寫函數,返回一個動態分配的 int 的vector。將此vector 傳遞給另一個函數,這個函數讀取標準輸入,將讀入的值保存在 vector 元素中。再將vector傳遞給另一個函數,打印讀入的值。記得在恰當的時刻delete vector。
#include <iostream>
#include <vector>
using namespace std;

vector<int>* build() {
	vector<int> *p(new vector<int>());
	return p;
}
vector<int>* write(vector<int> *p) {
	int i;
	while (cin >> i)
		p->push_back(i);
	return p;
}
void read(vector<int> *p) {
	for (int i : *p)
		cout << i << endl;
	delete p;
}
int main() {
	read(write(build()));
	return 0;
}

練習12.7

  • 重做上一題,這次使用 shared_ptr 而不是內置指針。
#include <iostream>
#include <vector>
#include <memory>
using namespace std;

shared_ptr<vector<int>> build() {
	auto p = make_shared<vector<int>>();
	return p;
}
shared_ptr<vector<int>>  write(shared_ptr<vector<int>> p) {
	int i;
	while (cin >> i)
		p->push_back(i);
	return p;
}
void read(shared_ptr<vector<int>> p) {
	for (int i : *p)
		cout << i << endl;
}
// 使用智能指針則無需注重釋放內存,在read函數內,p作爲函數內的局部變量,將被自動銷燬
int main() {
	read(write(build()));
	return 0;
}

練習12.8

  • 下面的函數是否有錯誤?如果有,解釋錯誤原因。

  •   bool b() 
      {
          int* p = new int;
          // ...
          return p;
      }
    

有錯誤。p在被轉化成bool類型後,將沒有機會再被釋放。將會導致內存泄露。

練習12.9

  • 解釋下面代碼執行的結果。

  •   int *q = new int(42), *r = new int(100);
      r = q;
      auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
      r2 = q2;
    

r指向的動態內存保留,但指針被銷燬,因此將會導致動態內存泄露。

r2被賦值給q2之後,r2指向的動態內存計數器清零,動態內存被銷燬。

12.1.3 節練習

練習12.10

  • 下面的代碼調用了第413頁中定義的process 函數,解釋此調用是否正確。如果不正確,應如何修改?

  •   shared_ptr<int> p(new int(42));
      process(shared_ptr<int>(p));
    

正確的。share_ptr<int>(p)的作用將返回一個p的臨時拷貝,而此時計數器會相應得增加到2。離開函數空間後,局部變量將被銷燬,計數器也降回至1。

練習12.11

  • 如果我們像下面這樣調用 process,會發生什麼?

  •   process(shared_ptr<int>(p.get()));
    

將會構造一個由get初始化的share_ptr對象。由於這個對象是由get獲得的內置指針構造的,因此兩者的計數器是分開的,因此當離開函數局部空間後,動態內存將被delete,導致p成爲空置指針。而在程序運行完畢後,p將被編譯器自動再次delete,將會導致指向的動態內存被二次銷燬。

練習12.12

  • p 和 q 的定義如下,對於接下來的對 process 的每個調用,如果合法,解釋它做了什麼,如果不合法,解釋錯誤原因:

  •   auto p = new int();
      auto sp = make_shared<int>();
      (a) process(sp);
      (b) process(new int());
      (c) process(p);
      (d) process(shared_ptr<int>(p));
      (a) 合法,將sp作爲參數傳遞給了process.
      (b) 不合法,內置指針不能隱式轉換成智能指針
      (c) 同上
      (d) 合法,但將會導致p指針指向的動態內存在離開函數局部空間後就被銷燬
    

練習12.13

  • 如果執行下面的代碼,會發生什麼?

  •   auto sp = make_shared<int>();
      auto p = sp.get();
      delete p;
    

sp成爲一個空置指針,其指向的動態內存已被銷燬。

12.1.4 節練習

練習12.14

  • 編寫你自己版本的用 shared_ptr 管理 connection 的函數。
void end_connection(connection *p) { disconnection(*p); }
void f(destination &d /* 其他參數 */){
    connection c = connect(&d);
    shared_ptr<connection> p(&c, end_connection);
}

練習12.15

  • 重寫第一題的程序,用 lambda (參見10.3.2節,第346頁)代替end_connection 函數。
void f(destination &d /* 其他參數 */){
    connection c = connect(&d);
    shared_ptr<connection> p(&c, [] (connection *p){ disconnection(*p); } ) ;
}

12.1.5 節練習

練習12.16

  • 如果你試圖拷貝或賦值 unique_ptr,編譯器並不總是能給出易於理解的錯誤信息。編寫包含這種錯誤的程序,觀察編譯器如何診斷這種錯誤。
error C2280: “std::unique_ptr<int,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)”: 嘗試引用已刪除的函數

練習12.17

  • 下面的 unique_ptr 聲明中,哪些是合法的,哪些可能導致後續的程序錯誤?解釋每個錯誤的問題在哪裏。

  •   int ix = 1024, *pi = &ix, *pi2 = new int(2048);
      typedef unique_ptr<int> IntP;
      (a) IntP p0(ix);
      (b) IntP p1(pi);
      (c) IntP p2(pi2);
      (d) IntP p3(&ix);
      (e) IntP p4(new int(2048));
      (f) IntP p5(p2.get());
      
      (a) 不合法,必須綁定到一個new返回的指針上
      (b) 能通過編譯,但可能出錯。因爲pi不是new出來的,因此在離開作用域後指針調用delete時會報錯。
      (c) 能通過編譯,但可能出錯。因爲當p2離開作用域後會delete掉動態內存,此時pi2就是空置指針了。
      (d) 能通過編譯,但可能出錯。因爲&ix不是new出來的,因此在離開作用域後指針調用delete時會報錯。
      (e) 合法。
      (f) 能通過編譯,但可能出錯。當P2或P5delete動態內存後,另一個指針就是空置指針了。還會引發二次delete的問題。
    

練習12.18

  • shared_ptr 爲什麼沒有 release 成員?

release操作將釋放指向的對象並返回指針,而share_ptr可能擁有多個指向同一個對象的指針,此時就會導致其他指針成爲空置指針。將會出現不必要的問題。

12.1.6 節練習

練習12.19

  • 定義你自己版本的 StrBlobPtr,更新 StrBlob 類,加入恰當的 friend 聲明以及 begin 和 end 成員。
#include <string>
#include <vector>
#include <memory>
#include <exception>
class StrBlobPtr;

class StrBlob {
	friend class StrBlobPtr;
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> il);
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	// 添加刪減元素
	void push_back(const std::string &t) { data->push_back(t); }
	void pop_back() 
	{check(0, "pop_back on empty StrBlob");
		data->pop_back();}	
	// 元素訪問
	std::string& front();
	const std::string& front() const;
	std::string& back();
	const std::string& back() const;
	StrBlobPtr begin();
	StrBlobPtr end();
private:
	std::shared_ptr < std::vector<std::string>> data;
	// 如果data[i]不合法,拋出一個異常
	void check(size_type i, const std::string &msg) const;
};

StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }
StrBlob::StrBlob(std::initializer_list<std::string> il) : data(std::make_shared<std::vector<std::string>>(il)) { }

void StrBlob::check(size_type i, const std::string &msg) const {
	if (i >= data->size())
		throw std::out_of_range(msg);
}

std::string& StrBlob::front(){
	// 如果vector爲空, check會拋出一個異常
	check(0, "front on empty StrBolb");
	return data->front();
}

std::string& StrBlob::back() {
	check(0, "back on empty StrBlob");
	return data->back();
}

const std::string& StrBlob::front() const {
	// 如果vector爲空, check會拋出一個異常
	check(0, "front on empty StrBolb");
	return data->front();
}

const std::string& StrBlob::back() const {
	check(0, "back on empty StrBlob");
	return data->back();
}


class StrBlobPtr {
public:
	StrBlobPtr() : curr(0) {}
	StrBlobPtr(StrBlob &a, std::size_t sz = 0) : wptr(a.data), curr(sz) {}
	std::string& deref() const;
	StrBlobPtr& incr(); //前綴遞增
	bool operator!=(const StrBlobPtr &a) { return a.curr != curr; }
private:
	std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
	std::weak_ptr<std::vector<std::string>> wptr;
	std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg) const {
	auto ret = wptr.lock();    // vector還存在嗎
	if (!ret)
		throw std::runtime_error("unbound StrBlobPtr");
	if (i >= ret->size())
		throw std::out_of_range(msg);
	return ret;    // 一切正常,返回指向vector的shared_ptr
}

std::string& StrBlobPtr::deref() const {
	auto p = check(curr, "dereference past end");
	return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::incr() {    // 前綴遞增,返回遞增後對象的引用
	check(curr, "increment past end of StrBlobPtr");
	++curr;
	return *this;
}

StrBlobPtr StrBlob::begin(){
	return StrBlobPtr(*this); 
}

StrBlobPtr StrBlob::end() {
	return StrBlobPtr(*this, data->size()); 
}

練習12.20

  • 編寫程序,逐行讀入一個輸入文件,將內容存入一個 StrBlob 中,用一個 StrBlobPtr 打印出 StrBlob 中的每個元素。
#include <iostream>
#include <fstream>
#include <string>
#include "StrBlob.h"
using namespace std;

int main() {
	ifstream file("Text.txt");
	StrBlob s;
	string str;
	while (getline(file, str))
		s.push_back(str);
	for (auto begin = s.begin(); begin != s.end(); begin.incr())
		cout << begin.deref() << endl;
	return 0;
}

練習12.21

  • 也可以這樣編寫 StrBlobPtr 的 deref 成員:

  •   std::string& deref() const {
      return (*check(curr, "dereference past end"))[curr];
      }
    

原版,可讀性更強。

練習12.22

  • 爲了能讓 StrBlobPtr 使用 const StrBlob,你覺得應該如何修改?定義一個名爲ConstStrBlobPtr 的類,使其能夠指向 const StrBlob。

修改構造函數使其接受const StrBlob&。修改begin和end函數爲const 成員函數。

#include <string>
#include <vector>
#include <memory>
#include <exception>
class ConstStrBlobPtr;

class StrBlob {
	friend class ConstStrBlobPtr;
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> il);
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	// 添加刪減元素
	void push_back(const std::string &t) { data->push_back(t); }
	void pop_back() 
	{check(0, "pop_back on empty StrBlob");
		data->pop_back();}	
	// 元素訪問
	std::string& front();
	const std::string& front() const;
	std::string& back();
	const std::string& back() const;
	ConstStrBlobPtr begin() const;     //兩個函數改爲const成員函數
	ConstStrBlobPtr end() const;
private:
	std::shared_ptr < std::vector<std::string>> data;
	// 如果data[i]不合法,拋出一個異常
	void check(size_type i, const std::string &msg) const;
};

StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }
StrBlob::StrBlob(std::initializer_list<std::string> il) : data(std::make_shared<std::vector<std::string>>(il)) { }

void StrBlob::check(size_type i, const std::string &msg) const {
	if (i >= data->size())
		throw std::out_of_range(msg);
}

std::string& StrBlob::front(){
	// 如果vector爲空, check會拋出一個異常
	check(0, "front on empty StrBolb");
	return data->front();
}

std::string& StrBlob::back() {
	check(0, "back on empty StrBlob");
	return data->back();
}

const std::string& StrBlob::front() const {
	// 如果vector爲空, check會拋出一個異常
	check(0, "front on empty StrBolb");
	return data->front();
}

const std::string& StrBlob::back() const {
	check(0, "back on empty StrBlob");
	return data->back();
}


class ConstStrBlobPtr {
public:
	ConstStrBlobPtr() : curr(0) {}
	ConstStrBlobPtr(const StrBlob &a, std::size_t sz = 0) : wptr(a.data), curr(sz) {}    // 接受參數改爲const StrBlob&
	const std::string& deref() const;    //根據邏輯,此處的返回值應爲const
	ConstStrBlobPtr& incr(); //前綴遞增
	bool operator!=(const ConstStrBlobPtr &a) { return a.curr != curr; }
private:
	std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
	std::weak_ptr<std::vector<std::string>> wptr;
	std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> ConstStrBlobPtr::check(std::size_t i, const std::string &msg) const {
	auto ret = wptr.lock();    // vector還存在嗎
	if (!ret)
		throw std::runtime_error("unbound StrBlobPtr");
	if (i >= ret->size())
		throw std::out_of_range(msg);
	return ret;    // 一切正常,返回指向vector的shared_ptr
}

const std::string& ConstStrBlobPtr::deref() const {
	auto p = check(curr, "dereference past end");
	return (*p)[curr];
}

ConstStrBlobPtr& ConstStrBlobPtr::incr() {    // 前綴遞增,返回遞增後對象的引用
	check(curr, "increment past end of StrBlobPtr");
	++curr;
	return *this;
}

ConstStrBlobPtr StrBlob::begin(){
	return ConstStrBlobPtr(*this);
}

ConstStrBlobPtr StrBlob::end() {
	return ConstStrBlobPtr(*this, data->size());
}

12.2.1 節練習

練習12.23

  • 編寫一個程序,連接兩個字符串字面常量,將結果保存在一個動態分配的char數組中。重寫這個程序,連接兩個標準庫string對象。
#include <iostream>
#include <string>
using namespace std;
#pragma warning( disable : 4996)   //關閉 strcpy不安全錯誤。

int main() {
	char* char1 = new char[strlen("Hello" "world") + 1](); // +1爲 '\0'
	strcpy(char1, "Hello ");
	strcat(char1, "world");
    delect[] char1;
	cout << char1 << endl;
	string str1{ "Hello " }, str2{ "world" };
	cout << str1 + str2 << endl;
}

練習12.24

  • 編寫一個程序,從標準輸入讀取一個字符串,存入一個動態分配的字符數組中。描述你的程序如何處理變長輸入。測試你的程序,輸入一個超出你分配的數組長度的字符串。
#include <iostream>
#include <string>
using namespace std;
#pragma warning( disable : 4996)   //關閉 strcpy不安全錯誤。

int main() {
	string str;
	char* chars = new char[2]();    // 動態數組自動增長
	cin >> str;
	strcpy(chars, str.c_str());
	cout << chars << endl;
	delete[] chars;
}

練習12.25

  • 給定下面的new表達式,你應該如何釋放pa?

  •   int *pa = new int[10];
    
delete[] pa;

12.2.2 節練習

練習12.26

  • 用allocator重寫第427頁中的程序。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main() {
	int n = 5;
	allocator<string> alloc;
	auto const p = alloc.allocate(n);
	string s;
	auto q = p;
	while (cin >> s && q != p + n)
		alloc.construct(q++, s);
	const size_t size = q - p;
	alloc.deallocate(p, n);
}

要找工作了,後面這具體的項目實踐就不做了,過基礎要緊。

有機會再回頭完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章