網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
一、vector迭代器失效問(wèn)題
1. insert迭代器失效
上文我們寫了insert的模擬實(shí)現(xiàn),最開始的版本是有許多Bug的,比如迭代器失效,最后經(jīng)過(guò)優(yōu)化修改實(shí)現(xiàn)了insert,這里我們以最初的版本為例,分析并解決迭代器失效問(wèn)題。如下:
void insert(iterator pos, const T& x)
{
//檢測(cè)參數(shù)合法性
assert(pos >= _start);
assert(pos <= _finish);
//檢測(cè)是否需要擴(kuò)容
if (_finish == _endofstorage)
{
size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapcacity);
}
//挪動(dòng)數(shù)據(jù)
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//插入指定的數(shù)據(jù)
*pos = x;
_finish++;
}
insert的迭代器失效分為兩大類:
1.1.擴(kuò)容導(dǎo)致野指針
我們給出兩組測(cè)試用例如下:
我們發(fā)現(xiàn)push_back尾插4個(gè)后調(diào)用insert會(huì)出現(xiàn)隨機(jī)值,而push_back尾插5個(gè)后調(diào)用insert就沒(méi)有問(wèn)題。
這里我們就不墨跡了,問(wèn)題就是擴(kuò)容導(dǎo)致pos迭代器失效,原因在于pos沒(méi)有更新,導(dǎo)致非法訪問(wèn)野指針。
上述當(dāng)尾插4個(gè)數(shù)字后,再頭插一個(gè)數(shù)字,發(fā)生擴(kuò)容,根據(jù)reserve擴(kuò)容機(jī)制,_ start和_ finish都會(huì)更新,但是這個(gè)插入的位置pos沒(méi)有更新,此時(shí)pos依舊執(zhí)行舊空間,再者reserve后會(huì)釋放舊空間,此時(shí)的pos就是野指針,導(dǎo)致*pos = x就是對(duì)非法訪問(wèn)野指針。因?yàn)閜os迭代器沒(méi)有更新,所以后續(xù)挪動(dòng)數(shù)據(jù)并沒(méi)有實(shí)現(xiàn),而插入數(shù)據(jù)是對(duì)釋放的空間進(jìn)行操作,同樣沒(méi)有意義。這也就是說(shuō)不論你在哪個(gè)位置插入,都沒(méi)有效果。
解決辦法:
可以通過(guò)創(chuàng)建變量n來(lái)計(jì)算擴(kuò)容前pos迭代器(指針)位置和_ start迭代器(指針)位置的相對(duì)距離,最后在擴(kuò)容后,讓_start再加上先前算好的相對(duì)距離n就是更新后的pos指針的位置了。
修正如下:
void insert(iterator pos, const T& x)
{
//檢測(cè)參數(shù)合法性
assert(pos >= _start);
assert(pos <= _finish);
//檢測(cè)是否需要擴(kuò)容,擴(kuò)容以后pos就失效了,需要更新一下
if (_finish == _endofstorage)
{
size_t n = pos - _start;//計(jì)算pos和start的相對(duì)距離
size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapcacity);
// 擴(kuò)容會(huì)導(dǎo)致pos迭代器失效,需要更新處理一下
pos = _start + n;//防止迭代器失效,要讓pos始終指向與_start間距n的位置
}
//挪動(dòng)數(shù)據(jù)
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//插入指定的數(shù)據(jù)
*pos = x;
_finish++;
}
此時(shí)的迭代器失效已經(jīng)解決了一部分,當(dāng)然還存在一個(gè)迭代器失效問(wèn)題,見(jiàn)下文:
1.2.迭代器指向位置意義改變
比如現(xiàn)在我要在所有的偶數(shù)前面 插入2,可是測(cè)試結(jié)果確是如下:
這里發(fā)生了斷言錯(cuò)誤,這段代碼發(fā)生了兩個(gè)錯(cuò)誤:
- 和上面的錯(cuò)誤一樣,首先it是指向原來(lái)的空間,當(dāng)insert插入新元素時(shí)會(huì)發(fā)生擴(kuò)容,原來(lái)的舊數(shù)據(jù)被拷貝到了新空間上,并且釋放舊空間,這也就意味著舊空間已經(jīng)被操作系統(tǒng)回收,而it一直是指向舊空間的,隨后遍歷it時(shí)就非法訪問(wèn)野指針,也就失效了。形參的改變不會(huì)影響實(shí)參,即使你內(nèi)部pos的指向改變了,但是并不會(huì)影響我外部的it。所以我們?nèi)匀粺o(wú)法通過(guò)it去訪問(wèn)元素。
- 為了解決上面的錯(cuò)誤,有人可能會(huì)說(shuō)提前reserve開辟足夠大的空間即可避免發(fā)生野指針的現(xiàn)象,但是又出現(xiàn)了一個(gè)新的問(wèn)題,看圖:
此時(shí)insert以后雖然沒(méi)有擴(kuò)容,it也沒(méi)有成為野指針,但是it指向位置意義變了,每插入一個(gè)數(shù)據(jù),it就指向插入數(shù)據(jù)的下一個(gè)數(shù)據(jù),導(dǎo)致我們這個(gè)程序重復(fù)插入20。
解決辦法:
給insert函數(shù)加上返回值即可解決,返回指向新插入元素的位置。
iterator insert(iterator pos, const T& x)
{
//檢測(cè)參數(shù)合法性
assert(pos >= _start);
assert(pos <= _finish);
//檢測(cè)是否需要擴(kuò)容,擴(kuò)容以后pos就失效了,需要更新一下
if (_finish == _endofstorage)
{
size_t n = pos - _start;//計(jì)算pos和start的相對(duì)距離
size_t newcapcacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapcacity);
// 擴(kuò)容會(huì)導(dǎo)致pos迭代器失效,需要更新處理一下
pos = _start + n;//防止迭代器失效,要讓pos始終指向與_start間距n的位置
}
//挪動(dòng)數(shù)據(jù)
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//插入指定的數(shù)據(jù)
*pos = x;
_finish++;
return pos;
}
我們調(diào)用函數(shù)模塊也得改動(dòng),讓it自己接收insert后的返回值:
//在所有的偶數(shù)前面插入2
void test_vector3()
{
vector<int> v;
v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = find(v.begin(), v.end(), 1);
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.insert(it, 20);
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
擴(kuò)展:
有的同學(xué)可能說(shuō),能否用引用,那樣就不用返回迭代器了,引用需要傳一個(gè)左值變量,但是如果我傳insert(bgein(),0)中的begin()是表達(dá)式的返回值,是一個(gè)臨時(shí)變量,具有常性。不能這樣使用。還有一些原因涉及到更深層次的問(wèn)題。
1.3.windows下VS中標(biāo)準(zhǔn)庫(kù)和Linux下g++中標(biāo)準(zhǔn)庫(kù)對(duì)insert迭代器失效的處理
VS:
針對(duì)于擴(kuò)容發(fā)生野指針類的迭代器失效,VS官方庫(kù)是直接斷言報(bào)錯(cuò)。把相同的代碼放到Linux的g++下面試試看呢?
Linux:
很明顯Linux這里可以直接訪問(wèn),甚至是可以修改。可見(jiàn)不同環(huán)境下對(duì)待迭代器失效的處理方式是不一樣的,windows下更加嚴(yán)格,Linux下比較佛系。
2. erase迭代器失效
和insert函數(shù)一樣,erase同樣會(huì)存在迭代器失效問(wèn)題,這里先給出erase模擬實(shí)現(xiàn)的代碼,存在一些問(wèn)題:
// 返回刪除數(shù)據(jù)的下一個(gè)數(shù)據(jù)
// 方便解決:一邊遍歷一邊刪除的迭代器失效問(wèn)題
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
//從pos + 1的位置開始往前覆蓋,即可完成刪除pos位置的值
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
}
_finish--;
}
- erase的失效都是意義變了,或者不在有效訪問(wèn)數(shù)據(jù)的有效范圍內(nèi)
- 一般不會(huì)使用縮容的方案,那么erase的失效,一般也不存在野指針的失效
2.1.迭代器失效指向位置意義改變
現(xiàn)在要對(duì)如下代碼進(jìn)行測(cè)試:
void test_vector2()
{
cpp::vector<int> v;
//v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
cout << v.size() << ":" << v.capacity() << endl;
vector<int>::iterator it = find(v.begin(), v.end(), 2);
if (it != v.end())
{
v.erase(it);
}
cout << *it << endl; // 讀
(*pos)++; // 寫
cout << *it << endl << endl;
cout << v.size() << ":" << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
}
運(yùn)行結(jié)果:
這里首先在尾插4個(gè)數(shù)據(jù)后,比較了下size和capacity的大小,此時(shí)是相等的,接下來(lái)刪除值為2的數(shù),此時(shí)* it就是刪除數(shù)字的下一個(gè)數(shù)據(jù),沒(méi)有問(wèn)題,并且有效數(shù)據(jù)size也少了一個(gè),后續(xù)修改*it也沒(méi)有問(wèn)題。
可是當(dāng)我要?jiǎng)h除值為4的數(shù)據(jù)呢,再執(zhí)行上述測(cè)試用例會(huì)是什么結(jié)果呢?
這里我總共就有4個(gè)數(shù)字,按理說(shuō)把最后一個(gè)數(shù)字刪去后,有效數(shù)字只有1、2、3,這里應(yīng)該不存在訪問(wèn)最后一個(gè)值的現(xiàn)象,但是此結(jié)果確實(shí)是刪掉4后又訪問(wèn)了4,離譜的是還修改了4為5,這就是erase典型的迭代器失效。因?yàn)槟憧臻g還沒(méi)有縮容,刪掉的4還存在,導(dǎo)致最終還能夠被訪問(wèn)。
總結(jié):
可見(jiàn)代碼確實(shí)是實(shí)現(xiàn)了刪除,但是程序訪問(wèn)出現(xiàn)問(wèn)題,原因就是erase后pos失效了,pos的意義變了,(但是在不同平臺(tái)下對(duì)于訪問(wèn)pos的反應(yīng)是不一樣的,因此我們使用的時(shí)候要特別小心,統(tǒng)一以失效的角度去看待)。但如果不訪問(wèn)pos指向的內(nèi)容就不會(huì)出問(wèn)題。比如我們沒(méi)有訪問(wèn)v.end()。
2.2.windows下VS中標(biāo)準(zhǔn)庫(kù)和Linux下g++中標(biāo)準(zhǔn)庫(kù)對(duì)erase迭代器失效的處理
這里我們以如上程序進(jìn)行對(duì)比vs和g++標(biāo)準(zhǔn)庫(kù)對(duì)erase迭代器失效的處理:
VS下:
VS環(huán)境下檢查非常嚴(yán)格, 直接強(qiáng)制檢查斷言錯(cuò)誤。
Linux下:
很明顯看出Linux下對(duì)于迭代器失效的檢查就松懈很多,不會(huì)報(bào)錯(cuò)。
結(jié)論如下:
- erase(pos)以后pos失效了,pos的意義變了,但是在不同平臺(tái)下面對(duì)于訪問(wèn)pos的反應(yīng)是不一樣的,我們用的時(shí)候要以失效的角度去看待此問(wèn)題。
- 對(duì)于insert和erase造成迭代器失效問(wèn)題,linux的g++平臺(tái)檢查并不是很嚴(yán)格,基本靠操作系統(tǒng)本身野指針越界檢查機(jī)制。windows下VS系列檢查更嚴(yán)格一些,使用一些強(qiáng)制檢查機(jī)制,意義變了可能會(huì)檢查出來(lái)。
- 雖然g++對(duì)于迭代器失效檢查時(shí)是并不嚴(yán)格,但是套在實(shí)際場(chǎng)景中,迭代器意義變了,也會(huì)出現(xiàn)各種問(wèn)題。
總結(jié):
大家可能發(fā)現(xiàn)我們實(shí)現(xiàn)的vector如果不使用std::命名空間封裝的話,結(jié)果和Linux下的結(jié)果一樣。這是因?yàn)閂S使用的STL標(biāo)準(zhǔn)庫(kù)是PJ版本,它檢查更為復(fù)雜,實(shí)現(xiàn)更為復(fù)雜;而我們使用的STL標(biāo)準(zhǔn)庫(kù)是SGI版,是Linux的g++編譯器使用的版本,也是侯捷老師的《STL源碼剖析》的版本。它檢查較為松懈,因?yàn)檫@里的迭代器就是原生指針,沒(méi)有進(jìn)行封裝檢查等。
下面分別給出三組測(cè)試用例:
- 1 2 3 4
- 1 2 3 4 5
- 1 2 2 3 4 5
void test_vector4()
{
//刪除所有的偶數(shù)
std::vector<int> v;
//v.reserve(10);
// 第一組測(cè)試用例:
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
it++;
}
for (auto e : v)
{
cout << e << " ";
}
}
在VS下用官方庫(kù)去測(cè)試會(huì)三組數(shù)據(jù)都崩潰:
而Linux下的結(jié)果如下:
畫圖演示錯(cuò)誤過(guò)程:
原因分析:
毫無(wú)疑問(wèn)上訴代碼會(huì)崩潰,因?yàn)閑rase后迭代器it所指向的位置失效,(雖然感覺(jué)是可以繼續(xù)使用的,但在vs下就是不可以使用,在Linux下就可以對(duì)這個(gè)位置進(jìn)行訪問(wèn)),所以下面我們用返回值來(lái)更新迭代器。
解決方案如下:
給erase加上返回值即可避免問(wèn)題,返回刪除元素的下一個(gè)位置。
修正如下:
// 返回刪除數(shù)據(jù)的下一個(gè)數(shù)據(jù)
// 方便解決:一邊遍歷一邊刪除的迭代器失效問(wèn)題
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
//從pos + 1的位置開始往前覆蓋,即可完成刪除pos位置的值
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
}
_finish--;
return pos;
}
我們調(diào)用函數(shù)模塊也得改動(dòng),讓it自己接收erase后的返回值:
void test4()
{
//刪除所有的偶數(shù)
std::vector<int> v;
//v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
it++;
}
}
for (auto e : v)
{
cout << e << " ";
}
}
分析:
erase刪除pos位置元素后,pos位置之后的元素會(huì)往前移動(dòng),沒(méi)有導(dǎo)致底層空間的改變,理論上講迭代器不會(huì)失效,但是如果pos位置剛好是最后一個(gè)元素,刪完之后pos剛好是end的位置,而end的位置是沒(méi)有有效元素的,那么pos就失效了。因此刪除vector中任意位置元素時(shí),vs均認(rèn)為該位置上迭代器失效了!也就是說(shuō)vector刪除一定會(huì)導(dǎo)致迭代器失效。
3.迭代器失效總結(jié)
vector迭代器失效有2種
1、擴(kuò)容,導(dǎo)致野指針失效
2、迭代器指向的位置意義變了
系統(tǒng)越界機(jī)制檢查,不一定能檢查到;編譯實(shí)現(xiàn)機(jī)制檢查,相對(duì)靠譜。
總結(jié):
- 對(duì)于insert和erase造成迭代器失效問(wèn)題,linux g++平臺(tái)檢查很松懈,基本依靠操作系統(tǒng)自身野指針越界檢查機(jī)制,windows下vs系列檢查更嚴(yán)格,使用一些強(qiáng)制檢查機(jī)制,意義變了也可能會(huì)檢查出來(lái)。
- 雖然g++對(duì)于erase迭代器失效檢查時(shí)非常雞肋的,但是套在實(shí)際場(chǎng)景中,迭代器意義變了,也會(huì)出現(xiàn)各種問(wèn)題,所以我們要有正確處理迭代器失效的方式,比如用函數(shù)返回值來(lái)更新迭代器。
- windows下vs系列對(duì)意義失效的檢查很雙標(biāo),由insert函數(shù)引起的意義失效檢查不出來(lái),而且可以訪問(wèn)pos位置,但是由erase函數(shù)引起的意義失效卻檢查很嚴(yán)格,絲毫不準(zhǔn)訪問(wèn)pos位置。但是Linux平臺(tái)下都檢查不出來(lái),都可以訪問(wèn)pos位置。
二、深淺拷貝問(wèn)題
1.拷貝構(gòu)造淺拷貝問(wèn)題
我們的拷貝構(gòu)造是存在一定問(wèn)題的,存在淺拷貝問(wèn)題,會(huì)導(dǎo)致程序崩潰。
// 拷貝構(gòu)造 v1(v)
// 傳統(tǒng)寫法
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
_start = new T[v.capacity()]; // 開辟一塊和v大小相同的空間
memcpy(_start, v._start, sizeof(T) * v.size()); //error
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
注意:
將容器當(dāng)中的數(shù)據(jù)一個(gè)個(gè)拷貝過(guò)來(lái)時(shí)不能使用memcpy函數(shù),當(dāng)vector存儲(chǔ)的數(shù)據(jù)是內(nèi)置類型或無(wú)需進(jìn)行深拷貝的自定義類型時(shí),使用memcpy函數(shù)是沒(méi)什么問(wèn)題的,但當(dāng)vector存儲(chǔ)的數(shù)據(jù)是需要進(jìn)行深拷貝的自定義類型時(shí),使用memcpy函數(shù)就會(huì)出現(xiàn)問(wèn)題。例如,當(dāng)vector存儲(chǔ)的數(shù)據(jù)是string類的時(shí)候。
并且vector當(dāng)中存儲(chǔ)的每一個(gè)string都指向自己所存儲(chǔ)的字符串。
如果此時(shí)我們使用的是memcpy函數(shù)進(jìn)行拷貝構(gòu)造的話,那么拷貝構(gòu)造出來(lái)的vector中每個(gè)string的成員變量的值,將與被拷貝的vector中每個(gè)string的成員變量的值相同,即兩個(gè)vector當(dāng)中的每個(gè)對(duì)應(yīng)的string成員都指向同一個(gè)字符串空間。
這顯然不是我們得到的結(jié)果,那么所給代碼是如何解決這個(gè)問(wèn)題的呢?
解決辦法:使用for循環(huán)把容器v中的數(shù)據(jù)一個(gè)一個(gè)拷貝過(guò)來(lái)。
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];
}
注意:_start[i] = _v[i] 本質(zhì)是調(diào)用string類的賦值運(yùn)算符重載函數(shù)進(jìn)行深拷貝。
代碼中看似是使用普通的“=”將容器當(dāng)中的數(shù)據(jù)一個(gè)個(gè)拷貝過(guò)來(lái),實(shí)際上是調(diào)用了所存元素的賦值運(yùn)算符重載函數(shù),而string類的賦值運(yùn)算符重載函數(shù)就是深拷貝,所以拷貝結(jié)果是這樣的:
代碼修改如下:
// 拷貝構(gòu)造 v1(v)
// 傳統(tǒng)寫法
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
_start = new T[v.capacity()]; // 開辟一塊和v大小相同的空間
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];
}
//memcpy(_start, v._start, sizeof(T) * v.size()); //error
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
總結(jié)一下: 如果vector當(dāng)中存儲(chǔ)的元素類型是內(nèi)置類型(int)或淺拷貝的自定義類型(Date),使用memcpy函數(shù)進(jìn)行進(jìn)行拷貝構(gòu)造是沒(méi)問(wèn)題的,但如果vector當(dāng)中存儲(chǔ)的元素類型是深拷貝的自定義類型(string),則使用memcpy函數(shù)將不能達(dá)到我們想要的效果。
2.擴(kuò)容淺拷貝問(wèn)題
接下來(lái)用先前模擬實(shí)現(xiàn)的vector來(lái)測(cè)試楊輝三角以此來(lái)解釋我們的深淺拷貝問(wèn)題,由于楊輝三角不太好理解,還是換個(gè)簡(jiǎn)單點(diǎn)的:
namespace vector_realize
{
/* class Solution {
public:
// 核心思想:找出楊輝三角的規(guī)律,發(fā)現(xiàn)每一行頭尾都是1,中間第[j]個(gè)數(shù)等于上一行[j-1]+[j]
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
vv.resize(numRows);// 先開辟楊輝三角的空間
for (size_t i = 0; i < vv.size(); ++i)
{
vv[i].resize(i + 1, 0);
vv[i][0] = vv[i][vv[i].size() - 1] = 1;// 每一行的第一個(gè)和最后一個(gè)都是1
}
for (size_t i = 0; i < vv.size(); ++i)
{
for (size_t j = 0; j < vv[i].size(); ++j)
{
if (vv[i][j] == 0)
{
vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
}
}
}
return vv;
}
};
void test_vector9()
{
vector<vector<int>> vvRet = Solution().generate(5);
for (size_t i = 0; i < vvRet.size(); ++i)
{
for (size_t j = 0; j < vvRet[i].size(); ++j)
{
cout << vvRet[i][j] << " ";
}
cout << endl;
}
cout << endl;
}*/
vector<vector<int>> vv;
vector<int> v(5, 1);
vv.push_back(v);
vv.push_back(v);
vv.push_back(v);
vv.push_back(v);
vv.push_back(v);
for (size_t i = 0; i < vv.size(); i++)
{
for (size_t j = 0; j < vv[i].size(); j++)
{
cout << vv[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
運(yùn)行結(jié)果:
這里如果我只插入4個(gè)元素就不會(huì)發(fā)生報(bào)錯(cuò),所以關(guān)鍵就在插入第五個(gè)元素改變了什么?改變?nèi)萘浚驗(yàn)槲覀償U(kuò)容的代碼有問(wèn)題。
把擴(kuò)容的代碼給出:
//reserve擴(kuò)容
void reserve(size_t n)
{
int oldSize = size();
if (capacity() < n)
{
// 1.開辟新空間
T* tmp = new T[n];
if (_start)
{
//2.拷貝元素
memcpy(tmp, _start, sizeof(T) * size());
//3. 釋放舊空間
delete[] _start;
}
_start = tmp;
}
// 這里_start的地址變了,而_finish還是原來(lái)的位置
//_finish = _start + size(); error
_finish = _start + oldSize;
_endofstorage = _start + n;
}
分析如下:
這里出錯(cuò)的原因在于擴(kuò)容,錯(cuò)在擴(kuò)容時(shí)調(diào)用的memcpy是淺拷貝,導(dǎo)致先前存儲(chǔ)的數(shù)據(jù)被memcpy后再delete就全刪掉變成隨機(jī)值了。vector調(diào)用析構(gòu)函數(shù)析構(gòu)掉原來(lái)的對(duì)象,每個(gè)對(duì)象又調(diào)用自身的析構(gòu)函數(shù),把指向的空間釋放掉,然后就會(huì)出現(xiàn)隨機(jī)值。
畫圖演示上述測(cè)試用例的原因:
總結(jié):
- vector中,當(dāng)T設(shè)計(jì)深淺拷貝的類型時(shí),如:string/vector等等,我們擴(kuò)容使用memcpy拷貝數(shù)據(jù)是存在淺拷貝問(wèn)題。
- memcpy是內(nèi)存的二進(jìn)制格式拷貝,將一段內(nèi)存空間中內(nèi)容原封不動(dòng)的拷貝到另外一段內(nèi)存空間中。
- 如果拷貝的是自定義類型的元素,memcpy即高效又不會(huì)出錯(cuò),但如果拷貝的是自定義類型元素,并且自定義類型元素中涉及到資源管理時(shí),就會(huì)出錯(cuò),因?yàn)閙emcpy的拷貝實(shí)際是淺拷貝。
解決方案:
reserve擴(kuò)容時(shí)不使用memcpy,改成for循環(huán)來(lái)解決:
//reserve擴(kuò)容
void reserve(size_t n)
{
int oldSize = size();
if (capacity() < n)
{
// 1.開辟新空間
T* tmp = new T[n];
if (_start)
{
//2.拷貝元素
// 這里直接用memcpy會(huì)有問(wèn)題,發(fā)生淺拷貝
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < oldSize; i++)
{
tmp[i] = _start[i]; // 本質(zhì)調(diào)用賦值運(yùn)算符重載進(jìn)行深拷貝
}
//3. 釋放舊空間
delete[] _start;
}
_start = tmp;
}
// 這里_start的地址變了,而_finish還是原來(lái)的位置
//_finish = _start + size(); error
_finish = _start + oldSize;
_endofstorage = _start + n;
}
分析:這里使用for循環(huán),看似是使用普通的“=”將容器當(dāng)中的數(shù)據(jù)一個(gè)個(gè)拷貝過(guò)來(lái),實(shí)際上是調(diào)用了所存元素的賦值運(yùn)算符重載函數(shù),而vector的賦值運(yùn)算符重載函數(shù)就是深拷貝,所以拷貝過(guò)程是這樣的:
使用這種方式就能完美避免上述問(wèn)題,我們運(yùn)行試一下:
總結(jié):
原文鏈接:https://blog.csdn.net/m0_64224788/article/details/128283755
相關(guān)推薦
- 2022-07-18 SQL?Server使用T-SQL進(jìn)階之公用表表達(dá)式(CTE)_MsSql
- 2022-12-04 pyecharts如何實(shí)現(xiàn)顯示數(shù)據(jù)為百分比的柱狀圖_python
- 2022-04-11 python文件讀寫操作小結(jié)_python
- 2022-09-29 Kotlin協(xié)程launch原理詳解_Android
- 2022-03-15 SpringCloud 使用OpenFeign調(diào)用其他服務(wù)的時(shí)候feign.RetryableExc
- 2022-12-09 C#中async和await的深入分析_C#教程
- 2022-07-31 Python常見(jiàn)的幾種數(shù)據(jù)加密方式_python
- 2022-01-29 yii SearchModel關(guān)于關(guān)聯(lián)表字段的查詢方法
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支