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);
}
要找工作了,後面這具體的項目實踐就不做了,過基礎要緊。
有機會再回頭完成。