個人感覺此次只需要掌握 shared_ptr<string> sp = make_shared<string>("sdfk");
及其sp.use_count(); sp.unique();
第 12 章 動態內存
概述. 動態內存和智能指針
在c++中,動態內存的管理是通過一對運算符來完成的:
new,在動態內存中爲對象分配空間並返回一個指向該對象的指針。我們可以選擇對對象進行初始化
delete,接受一個動態對象的指針,銷燬該對象,並釋放與之相關的內存。
動態分配內存帶來了許多問題,比如忘記釋放的內存泄漏,提前釋放的指針非法訪問內存。
c++11新標準庫提供了兩種智能指針類型來管理動態對象,只能指針的行爲類似常規指針,區別是它自動釋放所指向的內存。
頭文件#include <memory>
兩種智能指針:
shared_ptr:允許多個指針指向同一個對象。
unique_ptr:獨佔所指向的對象。
伴隨類weak_ptr:指向share_ptr所管理的對象。
//shared_ptr和unique_ptr都支持的操作
//空智能指針。可以指向string類型的對象
shared_ptr<string>sp;
unique_ptr<string>up;
sp //sp可以作爲條件判斷sp是否指向一個對象
*sp //解引用sp,獲得它指向的對象
sp->mem //等價於(*sp).mem
sp.get() //返回sp中所報存的指針。要小心使用,所智能指針釋放了對象,則返回的指針所指向的對象也不存在了。
swap(sp,sq)
sp.swap(sq) //交換sp和sq中的指針
//shared_ptr支持的操作
make_shared<T>(args) //返回一個shared_ptr,指向一個動態分配的類型爲T的對象,使用args初始化對象
shared_ptr<T>p(q) //p是shared_ptr q的拷貝,此操作會遞增q中的記數器,q中的指針必須能轉換成T*
p = q //p和q都是shared_ptr,所保存的指針必須能相互轉換,此操作會遞減p的引用計數,增加q的引用計數,p引用計數爲0時會釋放其管理的內存。
p.use_count() //返回與p共享智能指針的數量,可能很慢主要用於調試
p.unique() //當p.use_count()爲1時,返回ture,否則返回false。
注意:
智能指針比較所指對象是否相同,只能通過get( )返回指針,比較指針的地址是否相等來判斷。
<1.make_shared函數#include <memory>
make_shared是一個非成員函數,具有給共享對象分配內存,並且只分配一次內存的優點,和顯式通過構造函數初始化(new)的shared_ptr相比較,後者需要至少兩次分配內存。這些額外的開銷有可能會導致內存溢出的問題
最安全的使用動態內存的方法是使用一個make_shared的函數。
此函數在動態內存中分配一個對象並初始化,返回指向此對象的shared_ptr。
我們可以認爲每個shared_ptr都有一個關聯的計數器,通常稱其爲引用計數,無論我們拷貝一個share_ptr,計數器都會遞增。
當我們給一個shared_ptr賦值或者shared被銷燬,計數器就會遞減。
當用一個shared_ptr初始化另外一個shared_ptr,或將它作爲參數傳遞給一個函數以及作爲函數的返回值(賦值給其他的),計數器都會遞增
一旦一個share_ptr的計數器變爲0,它就會釋放自己所管理的對象。
!注意標準庫是用計數器還是其他數據結構來記錄有多少個指針共享對象由標準庫來決定,關鍵是智能指針類能記錄有多少個shared_ptr指向相同的對象,
並能在恰當的時候自動釋放對象。
補充:智能指針和make_shared分配內存初始化
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
int main()
{
shared_ptr<string>sp;
make_shared<string>(); //動態分配內存默認初始化,必須要有括號
//make_shared<string>ms; //error:動態分配內存!!聯想c語言malloc也沒有起名字 - -。
make_shared<string>("a"); //動態分配內存值初始化
shared_ptr<string>sp2 = make_shared<string>(); //初始化智能指針
shared_ptr<string>sp3 = make_shared<string>("b");//初始化智能指針
}
例子:
[cpp] view plain copy
#include <iostream>
#include <memory>
#include <string>
using namespace std;
shared_ptr<string> fun1(shared_ptr<string> sp5)//傳遞參數會構造一個,計數器遞增,函數運行結束後釋放
{
auto sp6 = sp5; //創建臨時並賦值,計數器遞增。
cout << "sp5 use_count:" << sp5.use_count() << endl;
cout << "sp5 is_unique:" << sp5.unique() << endl;
return sp6;
}
int main()
{
shared_ptr<string>sp = make_shared<string>("aa");
auto sp3 = make_shared<string>(10,'a');//通常使用auto來簡化定義一個對象來保存make_shared的結果,這種方式比較簡單。
cout << "sp use_count:" << sp.use_count() << endl;
auto sp2(sp); //拷貝sp,count計數會增加
cout << "sp use_count:" << sp.use_count() << endl;
cout << "sp is_unique:" << sp.unique() << endl;
sp2 = sp3; //賦值sp2,計數會減少
cout << "sp use_count:" << sp.use_count() << endl;
cout << "sp is_unique:" << sp.unique() << endl;
auto sp4(sp3);
cout << "sp3 use_count:" << sp3.use_count() << endl;
cout << "sp3 is_unique:" << sp3.unique() << endl;
sp = sp3;//sp指向sp3指向的,sp指向的被銷燬。
cout << "sp use_count:" << sp.use_count() << endl;
cout << "sp is_unique:" << sp.unique() << endl;
auto sp7 = fun1(sp);
cout << "sp7 use_count:" << sp.use_count() << endl;
cout << "sp7 is_unique:" << sp.unique() << endl;
}
view
plain cop
注意! sp1 = sp2; sp2計數器值增加,右值的計數器增加,左值指向的對象的計數器減少,減少爲0時自動釋放對象
<2.shared_ptr 自動銷燬所管理的對象
當指向對象的最後一個shared_ptr 被銷燬時,shared_ptr 類會自動銷燬此對象。它是通過特殊的成員函數析構函數來控制對象銷燬時做什麼操作。
shared_ptr 的析構函數會遞減它所指向的對象的引用計數,如果引用計數變爲0,shared_ptr 的函數就會銷燬對象,並釋放它佔用的資源。
對於一塊內存,shared_ptr 類保證只要有任何shared_ptr 對象引用它,它就不會被釋放。
如果我們忘記了銷燬程序不再需要的shared_ptr,程序仍然會正確運行,但會浪費內存
注意!:如果你將shared_ptr存放於一個容器中,而後不在需要全部元素,而只使用其中的一部分,要記得調用erase刪除不再需要的那些元素。
注意!:將一個shared_ptr 賦予另一個shared_ptr 會遞增賦值號右側的shared_ptr 的引用計數,而遞減左側shared_ptr 的引用計數,如果一個shared_ptr 引用技術
變爲0時,它所指向的對象會被自動銷燬。
<3.使用了動態生存期的資源的類
程序使用動態內存出於以下三種原因
1.程序不知道自己需要使用多少對象
2.程序不知道所需對象的準確類型
3.程序需要在多個對象間共享數據。
使用動態內存的一個常見的原因是允許多個對象共享相同的狀態。
重點例子!!
我們希望定義一個Blob類,保存一組元素,與容器不同,我們希望Blob對象的不同拷貝之間共享相同的元素。既當我們拷貝一個Blob時,
原Blob對象及其拷貝應該引用相同的底層元素。
定義一個管理string的類,命名爲StrBlob。
-
#include <iostream>
-
#include <string>
-
#include <memory> //智能指針和動態分配內存
-
#include <vector>
-
#include <initializer_list> //初始值列表
-
#include <stdexcept>
-
-
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() { return data->empty(); }
-
-
void push_back(const std::string &s){ data->push_back(s); }
-
void pop_back();
-
-
std::string& front();
-
std::string& back();
-
const std::string& front()const;
-
const std::string& back() const;
-
-
private:
-
std::shared_ptr<std::vector<std::string>> data;
-
-
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);
-
}
-
-
const std::string& StrBlob::back()const
-
{
-
check(0, "back on empty StrBlob");
-
return data->back();
-
}
-
<span style="color:#FF0000;">
-
-
-
-
-
-
-
std::string& StrBlob::back()
-
{
-
const auto &s = static_cast<const StrBlob*>(this)->back();
-
return const_cast<std::string&>(s);
-
}
-
-
const std::string& StrBlob::front()const
-
{
-
check(0, "front on empty StrBlob");
-
return data->front();
-
}
-
-
std::string& StrBlob::front()
-
{
-
const auto &s = static_cast<const StrBlob*>(this)->front();
-
return const_cast<std::string&>(s);
-
}
-
-
void StrBlob::pop_back()
-
{
-
check(0, "pop_back on empty StrBlob");
-
data->pop_back();
-
}
-
-
int main()
-
{
-
std::shared_ptr<StrBlob>sp;
-
StrBlob s({"wang","wei","hao"});
-
StrBlob s2(s);
-
std::string st = "asd";
-
s2.push_back(st);
-
-
std::cout << s2.front() << std::endl;
-
std::cout << s2.back() << std::endl;
-
}
可以輸出s和s2的size( )是相等的證明他們共享的是同一塊內存。
2.直接管理內存
<1.使用new動態分配和初始化對象
-
#include <iostream>
-
#include <string>
-
#include <vector>
-
-
using namespace std;
-
-
int main()
-
{
-
-
int *p = new int;
-
int *p2 = new int(10);
-
string *p3 = new string(10,'a');
-
vector<string> *p4 = new vector<string>{"a","b","c"};
-
-
string *p5 = new string();
-
string *p6 = new string;
-
int *p7 = new int;
-
-
-
-
auto p8 = new auto("abc");
-
auto p9 = new auto{1,2,3};
-
-
-
-
const string *p10 = new const string("aha");
-
-
-
int *p11 = new int;
-
int *p12 = new (nothrow) int;
-
}
<2.delete注意:
delete p;釋放p所指向的對象的那塊內存區域,釋放後p仍然指向那塊區域(測試時輸出的地址仍然相同),但是釋放後輸出的對象已無效。
一般我們可以在釋放delete p後, p = nullptr。這樣明確指針不指向其他的區域。
空懸指針:指向一塊曾經保存數據現在已經無效的內存的指針。
堅持只使用智能指針,就可以避免所有的問題,對於一塊內存,只有在沒有任何智能指針指向它的情況下,智能指針纔會釋放它。
-
#include <memory>
-
#include <iostream>
-
-
using namespace std;
-
-
int main()
-
{
-
-
int *p = new int(20);
-
int *p2 = new int(40);
-
p = p2;
-
-
auto p3 = make_shared<int>(20);
-
auto p4 = make_shared<int>(30);
-
p3 = p4;
-
}
<3.shared_ptr和new結合使用。
<<1.
可以用new來初始化智能指針
接受指針參數的智能指針構造函數是explicit(避免隱式轉換)的,我們不能將一個內置指針隱式轉換爲一個智能指針。
默認情況下,一個用來初始化智能指針的普通指針必須指向一塊動態分配的內存,因爲智能指針默認使用delete釋放它所關聯的對象。
如果將智能指針綁定到其他類型的指針上,我們必須自己定義自己的釋放操作。
-
#include <iostream>
-
#include <memory>
-
-
using namespace std;
-
-
shared_ptr<int> clone(int p)
-
{
-
return shared_ptr<int>(new int(p));
-
-
}
-
-
int main()
-
{
-
shared_ptr<int>p(new int(10));
-
cout << "p:" << *p << endl;
-
cout << "p:" << p.unique() << endl;
-
cout << "p:" << p.use_count() << endl;
-
-
-
-
-
int *p2 = new int(20);
-
shared_ptr<int>p3(p2);
-
cout << "p3:" << *p3 << endl;
-
cout << "p3:" << p3.unique() << endl;
-
cout << "p3:" << p3.use_count() << endl;
-
unique_ptr<int>p4(new int(40));
-
-
-
-
p3.reset();
-
int *p6 = new int(50);
-
p3.reset(p6);
-
cout << "p3:" << *p3 << endl;
-
cout << "p3:" << p3.unique() << endl;
-
cout << "p3:" << p3.use_count() << endl;
-
-
-
-
-
-
-
-
}
<<2.不要混用智能指針和普通指針。
shared_ptr 可以協調對象的析構(也就是計數爲0釋放),僅限於自身的拷貝,所以推薦使用make_shared而不是new。
在分配對象時就將對象綁定在shared_ptr上面。
當將一個shared_ptr 綁定到一個普通指針時,我們就將內存管理交給了shared_ptr,之後我們就不應該使用內置指針來訪問shared_ptr指向的內存了。
使用內置指針來訪問智能指針所附則的對象是非常危險的,我們不知道對象何時被銷燬。
<<3.也不要使用get初始化另一個智能指針或者爲智能指針賦值
智能指針定義了一個名爲get的函數,返回一個普通類型的指針。
目的:向不能使用智能指針的代碼傳遞一個內置指針,使用get返回的指針的代碼不能delete此指針。
將另一個智能指針綁定到get返回的指針也是錯誤的。
永遠不要用get初始化另一個智能指針或者爲另一個智能指針賦值。
普通指針不能自動轉化爲智能指針。
爲什麼使用get返回的指針不能使用delete
-
{
-
auto p = make_shared<int>(20);
-
auto q = p.get();
-
delete q;
-
}
p是一個智能指針,在函數塊結束後會自動調用內部delete釋放動態申請的空間,然而我們又delete了一次,等於釋放了兩次空間。
*** Error in `./a.out': double free or corruption (out): 0x00007fff21931150 ***
書上的一個例子,有點問題記錄下
-
#include <memory>
-
#include <iostream>
-
-
using namespace std;
-
-
int main()
-
{
-
shared_ptr<int>p(new int(42));
-
int *q = p.get();
-
cout << "count:" << p.use_count() << endl;
-
-
{
-
auto t = shared_ptr<int>(q);
-
cout << "count:" << t.use_count() << endl;
-
}
-
int foo = *p;
-
cout << foo << endl;
-
}
書上沒有auto t,按照書上foo可以正常使用,估計是編譯器優化。
書上意思是智能指針t也指向q所指向的內存,但是引用計數都是1,函數塊結束後,t被釋放,內存也被delete,那麼p指向的未定義了。
<<4.智能指針和異常
使用智能指針,即使程序塊過早結束,智能指針類也能確保內存不再需要時將其釋放。
但是普通指針就不會
-
void fun( )
-
{
-
int *p = new int(42);
-
-
delete p;
-
}
<<5.智能指針和啞類
標準很多都定義了析構函數,負責清理對象使用的資源,但是一些同時滿足c和c++的設計的類,通常都要求我們自己來釋放資源。
通過智能指針可以很好的解決這個問題
舉個網絡連接的例子:
-
connection connect(*destination);
-
void disconnect(connect);
-
void f(destination &d)
-
{
-
connection c = connect(&d);
-
disconnect(d);
-
}
-
-
connection connect(*destination);
-
void end_disconnect(connection*p) {disconnect(p);}
-
void f(destination &d)
-
{
-
connection c = connect(&d);
-
shared_ptr<connection>p(&d, end_connect);
-
-
}
demo,用string代替connection類型。
-
#include <iostream>
-
#include <string>
-
#include <memory>
-
-
using namespace std;
-
typedef string connection;
-
-
connection& connect(connection *s)
-
{
-
cout << "正在連接..." << endl;
-
s = new connection("connect");
-
return *s;
-
}
-
-
void disconnect(connection *s)
-
{
-
cout << "正在斷開連接..." << endl;
-
}
-
-
int main()
-
{
-
connection p;
-
connection *d;
-
p = connect(d);
-
shared_ptr<connection>sp(&p,disconnect);
-
}
這樣做即使我們忘記寫斷開連接或者中間發生了異常都會保證執行斷開連接的代碼。
!注意:智能指針陷阱
*不使用相同的內置指針值初始化(或reset)多個智能指針 //多個智能指針還是單獨的指向內置指針的內存,use_count分別爲1
*不delete get( )返回的指針 //兩次delete釋放,智能指針內部也會delete
*不使用get( )初始化或reset另一個智能指針 //free( ): invalid pointer:也是多次釋放
*如果你使用get( )返回的指針,記住當最後一個對應的智能指針銷燬後,你的指針就變得無效了
*如果你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器(刪除函數向上面的disconnect( ))。
課後題12.15
使用lambda改寫connect函數。
-
#include <iostream>
-
#include <string>
-
#include <memory>
-
#include <functional>
-
#include <algorithm>
-
-
using namespace std;
-
typedef string connection;
-
-
connection& connect(connection *s)
-
{
-
cout << "正在連接..." << endl;
-
s = new connection("connect");
-
return *s;
-
}
-
-
void disconnect(connection *s)
-
{
-
cout << "正在斷開連接..." << endl;
-
}
-
-
int main()
-
{
-
connection p;
-
connection *d;
-
p = connect(d);
-
-
-
-
shared_ptr<connection>sp(&p, [](connection *s) { disconnect(s); });
-
}
shared_ptr 的傳遞刪除器(deleter)方式比較簡單, 只需要在參數中添加具體的刪除器函數名, 即可; 注意是單參數函數;
unique_ptr 的刪除器是函數模板(function template), 所以需要在模板類型傳遞刪除器的類型(即函數指針(function pointer)), 再在參數中添加具體刪除器;
2.unique_ptr
介紹:一個unique_ptr 擁有它所指向的對象,和shared_ptr不同,某個時刻只能有一個unique_ptr 指向一個給定對象,當unique_ptr 被銷燬時,對象也被銷燬
<1.
unique沒有類似make_shared,必須手動new,將其綁定
由於unique_ptr獨佔它所指向的對象,因此他不支持普通的拷貝和賦值
但是有種特殊的拷貝可以支持:我們可以拷貝或賦值一個即將要被銷燬的unique_ptr。
-
#include <memory>
-
#include <iostream>
-
#include <string>
-
-
using namespace std;
-
-
unique_ptr<int> clone(int p)
-
{
-
unique_ptr<int>q(new int(p));
-
return q;
-
-
}
-
-
int main()
-
{
-
unique_ptr<string>p(new string("aaa"));
-
shared_ptr<string>p2(new string("aaa"));
-
-
-
unique_ptr<string>p5;
-
string s = "a";
-
-
-
-
unique_ptr<string>p6(p.release());
-
unique_ptr<string>p7;
-
p7.reset(p6.release());
-
-
-
int i = 10;
-
clone(i);
-
}
在早的版本中提供了auto_ptr的類,它有unique_ptr 的部分特性,但是不能在容器中保存auto_ptr, 也不能在函數中返回 auto_ptr, 編寫程序時應該使用unique_ptr.
<2.向unique_ptr 傳遞刪除器
類似shared_ptr, unique_ptr 默認情況下用delete釋放它指向的對象,和shared_ptr 一樣我們可以重載一個unique_ptr 中默認的刪除器類型。
重載一個unique_ptr 中的刪除器會影響到unique_ptr 類型及如何構造該類型的對象,
我們必須在尖括號中unique_ptr 指向類型之後提供刪除器的類型,在創建或reset一個這種unique_ptr類型的對象時,必須提供一個指定類型的可調用對象(刪除器)
-
#include <memory>
-
#include <iostream>
-
-
using namespace std;
-
-
typedef int connection;
-
-
connection* connect(connection *d)
-
{
-
cout << "正在連接..." << endl;
-
d = new connection(40);
-
return d;
-
}
-
-
void disconnect(connection *p)
-
{
-
cout << "斷開連接..." << endl;
-
}
-
-
int main()
-
{
-
connection *p,*p2;
-
p2 = connect(p);
-
cout << p << endl;
-
cout << *p2 << endl;
-
unique_ptr<connection, decltype(disconnect)*>q(p2,disconnect);
-
-
-
}
注意:
p.release( ); //error:p2不會釋放內存,而且丟失了指針
auto q = p.release( ); //q 是int * 類型, 記得delete釋放q
c++11
3.weak_ptr
weak_ptr 是一種不控制對象生存期的智能指針,它指向由一個shared_ptr 管理的對象。
-
#include <memory>
-
#include <iostream>
-
-
using namespace std;
-
-
int main()
-
{
-
shared_ptr<int>p0 = make_shared<int>(42);
-
auto p1 = p0;
-
cout << "p1 count:" << p1.use_count() << endl;
-
weak_ptr<int>p2;
-
weak_ptr<int>p3(p1);
-
p3 = p0;
-
p2 = p3;
-
cout << "p2 count:" << p2.use_count() << endl;
-
-
p2.reset();
-
cout << "p2 count:" << p2.use_count() << endl;
-
cout << "p3 count:" << p3.use_count() << endl;
-
-
-
cout << "p2 expired:" << p2.expired() << endl;
-
cout << "p3 expired:" << p3.expired() << endl;
-
-
-
auto p4 = p2.lock();
-
cout << "p4 count:" << p4.use_count() << endl;
-
auto p5 = p3.lock();
-
cout << "p5 count:" << p5.use_count() << endl;
-
}
!注意:
當我們創建一個weak_ptr 必須用一個 shared_ptr 初始化。
引入lock和expired是防止在weak_ptr 不知情的情況下,shared_ptr 被釋放掉
weak_ptr 不會更改shared_ptr 的引用計數。
std::weak_ptr
是一種智能指針,它對被 std::shared_ptr 管理的對象存在非擁有性(“弱”)引用。在訪問所引用的對象前必須先轉換爲 std::shared_ptr。
std::weak_ptr
用來表達臨時所有權的概念:當某個對象只有存在時才需要被訪問,而且隨時可能被他人刪除時,可以使用std::weak_ptr
來跟蹤該對象。需要獲得臨時所有權時,則將其轉換爲 std::shared_ptr,此時如果原來的 std::shared_ptr被銷燬,則該對象的生命期將被延長至這個臨時的 std::shared_ptr 同樣被銷燬爲止。
此外,std::weak_ptr
還可以用來避免 std::shared_ptr 的循環引用。
例子:
定義一個StrBlobPtr(內部weak_ptr)類打印StrBlob中的元素
上面的StrBlobPtr 例子的改進。
StrBlobPtr內部是weak_ptr實現的,它實際上就是一個助手類,作用就是類似一個旁觀者,一直觀測StrBlob的資源使用情況
-
-
-
-
-
-
#include <iostream>
-
#include <vector>
-
#include <string>
-
#include <initializer_list>
-
#include <memory>
-
#include <stdexcept>
-
#include <fstream>
-
#include <sstream>
-
-
class StrBlob;
-
class StrBlobPtr;
-
-
-
class StrBlob
-
{
-
public:
-
friend class StrBlobPtr;
-
typedef std::vector<std::string>::size_type size_type;
-
StrBlob();
-
StrBlob(std::initializer_list<std::string>il);
-
size_type size() { return data->size(); }
-
std::string& front();
-
std::string& back();
-
const std::string& front()const;
-
const std::string& back()const;
-
void push_back(const std::string &s) { data->push_back(s); }
-
void pop_back();
-
-
-
-
-
private:
-
void check(size_type sz, std::string msg)const;
-
std::shared_ptr<std::vector<std::string>>data;
-
};
-
-
std::string& StrBlob::front()
-
{
-
const auto &s = static_cast<const StrBlob*>(this)->front();
-
return const_cast<std::string&>(s);
-
}
-
-
std::string& StrBlob::back()
-
{
-
const auto &s = static_cast<const StrBlob*>(this)->back();
-
return const_cast<std::string&>(s);
-
}
-
-
const std::string& StrBlob::front()const
-
{
-
check(0, "front on empty vector");
-
return data->front();
-
}
-
-
const std::string& StrBlob::back()const
-
{
-
check(0, "back on empty vector");
-
return data->back();
-
}
-
-
void StrBlob::check(size_type sz, std::string msg)const
-
{
-
if(sz >= data->size())
-
throw std::out_of_range(msg);
-
}
-
-
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)) { }
-
-
-
-
-
-
class StrBlobPtr
-
{
-
public:
-
friend StrBlob;
-
StrBlobPtr():curr(0){ }
-
StrBlobPtr(StrBlob &s, std::size_t sz = 0):
-
wptr(s.data), curr(sz){ }
-
std::string& deref()const;
-
StrBlobPtr& incr();
-
-
private:
-
std::shared_ptr<std::vector<std::string>> check(std::size_t i, const std::string &msg)const;
-
std::weak_ptr<std::vector<std::string>> wptr;
-
std::size_t curr;
-
};
-
-
StrBlobPtr& StrBlobPtr::incr()
-
{
-
check(curr, "increment past end of StrBlobPtr");
-
++curr;
-
return *this;
-
}
-
-
std::string& StrBlobPtr::deref()const
-
{
-
auto p = check(curr, "dereference past end");
-
return (*p)[curr];
-
}
-
-
-
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg)const
-
{
-
auto ret = wptr.lock();
-
if(!ret)
-
throw std::runtime_error("unbound StrBlobPtr");
-
if(i >= ret->size())
-
throw std::out_of_range(msg);
-
return ret;
-
}
-
-
-
int main(int argc, char*argv[])
-
{
-
std::fstream is(argv[1]);
-
std::string s;
-
StrBlob S;
-
while(std::getline(is, s))
-
{
-
std::string temp;
-
std::istringstream ist(s);
-
while(!ist.eof())
-
{
-
ist >> temp;
-
S.push_back(temp);
-
}
-
}
-
-
std::cout << "size:" << S.size() << std::endl;
-
-
StrBlobPtr sp(S);
-
for(auto i = 0; i < S.size(); ++i)
-
{
-
std::cout << sp.deref() << std::endl;
-
sp.incr();
-
}
-
}
4.動態數組
new和delete一次只能分配和釋放一個對象,但有時我們需要一次爲很多對象分配內存的功能
C++和標準庫引入了兩種方法,另一種new 和 allocator。
使用allocator 通常會提供更好的性能和更靈活的內存管理能力。
<1.new和數組
動態數組不是數組類型。
-
#include <iostream>
-
#include <memory>
-
-
using namespace std;
-
-
typedef int arr[10];
-
-
int main()
-
{
-
int *p = new int[10];
-
int *p2 = new arr;
-
for(int i = 0; i < 10; i++)
-
{
-
p[i] = i;
-
}
-
for(int i = 0; i < 10; i++)
-
cout << p[i] << " ";
-
cout << endl;
-
-
-
-
-
int *pi = new int[10];
-
int *pi2 = new int[10]();
-
string *ps = new string[10];
-
string *ps2 = new string[10]();
-
-
int *pi3 = new int[10]{1,2,3,4,5,6,7,8,9,0};
-
string *ps3 = new string[10]{"a","b","c","d","e","f","g","h","i",string(3,'x')};
-
-
delete []pi3;
-
-
-
-
int *p5 = new int[10];
-
-
unique_ptr<int[]> up(p5);
-
for(int i = 0; i < 10; ++i)
-
cout << up[i] << " ";
-
cout << endl;
-
-
shared_ptr<int>sp(new int[10], [](int *p) { delete []p;});
-
-
cout << (*sp.get()) << endl;
-
-
}
課後題12.24
-
#include <iostream>
-
#include <string>
-
-
using namespace std;
-
-
int main()
-
{
-
string s1;
-
cin >> s1;
-
string *p = new string(s1);
-
const char *p1 = new char[s1.size()];
-
p1= (*p).c_str();
-
cout << "*p1 " << *p1 << endl;
-
cout << "p1 ";
-
for(int i = 0; i < s1.size(); ++i)
-
cout << p1[i] << " ";
-
cout << endl;
-
const char *p2 = new char[10];
-
string ss;
-
ss = "aaaaaaaaaaaaaaaaaaaaaa";
-
p2 = ss.c_str();
-
cout << "p2 ";
-
for(int i = 0; i < ss.size(); ++i)
-
cout << p2[i] << " ";
-
cout << endl;
-
}
<2.使用allocator類
引入allocator的原因是new類上的缺陷
new它將內存分配和對象構造結合到了一起
比如:string *p = new string;
new是現在找一塊內存分配,不夠繼續malloc,在分配內存的地址上調用構造函數,delete也一樣,在釋放內存的時候也會調用析構函數。
內置類型要指定初值。
但是如果我們希望指定它的初值,不讓它調用默認構造函數new就不可行了,而且本身調用了一次構造函數,然後我們賦值了一次。
更重要的是,沒有默認構造函數的就不能動態分配內存了。
-
#include <iostream>
-
#include <memory>
-
#include <string>
-
-
using namespace std;
-
-
int main()
-
{
-
-
allocator<string>alloc;
-
-
string *const p = alloc.allocate(10);
-
cout << sizeof(p) << endl;
-
-
-
alloc.deallocate(p,10);
-
-
-
-
-
-
allocator<string>alloc2;
-
auto const p2 = alloc2.allocate(10);
-
auto q = p2;
-
auto q2 = p2;
-
-
alloc2.construct(q++, "sssss");
-
cout << *q2++ << endl;
-
alloc2.construct(q++, "he");
-
cout << *q2++ << endl;
-
alloc2.construct(q++, 10, 'x');
-
cout << *q2 << endl;
-
-
while(q2 != p2)
-
alloc2.destroy(q2--);
-
-
alloc2.deallocate(p2, 10);
-
-
-
}
標準庫還爲allocator定義了兩個伴隨算法
在未初始化的內存中創建對象,都定義在頭文件memory
-
uninitialized_copy(b,e,b2) b,2是輸入容器的迭代器,b2是內存的起始地址,要保證空間足夠
-
uninitialized_copy_n(b,n,b2) b是輸入容器的起始迭代器,複製n個,複製到以b2爲起始地址的動態內存中
-
uninitialized_fill(b,e,t) b,e是動態內存的起始和終止位置,t是要fill的元素
-
uninitialized_fill_n(b,n,t) b是動態內存的起始,fill n個,t是要fill的元素
-
#include <iostream>
-
#include <string>
-
#include <vector>
-
#include <memory>
-
-
using namespace std;
-
-
int main()
-
{
-
-
vector<string>ivec(10,"a");
-
allocator<string>alloc;
-
auto const p = alloc.allocate(ivec.size()*4);
-
auto q = uninitialized_copy(ivec.begin(),ivec.end(), p);
-
auto q2 = q;
-
while(q-- != p)
-
cout << *q << " ";
-
cout << endl;
-
uninitialized_fill_n(q2, ivec.size(), "b");
-
for(auto i = 0; i < ivec.size(); ++i)
-
cout << *q2++ << " ";
-
cout << endl;
-
-
vector<string>ivec2(10,"c");
-
auto q3 = uninitialized_copy_n(ivec2.begin(),10,q2);
-
for(auto i = 0; i < ivec2.size(); ++i)
-
cout << *q2++ << " ";
-
cout << endl;
-
uninitialized_fill(q3,q3+10, "d");
-
for(auto i = 0; i < ivec2.size(); ++i)
-
cout << *q3++ << " ";
-
cout << endl;
-
-
}