![](/img/trans.png)
[英]Why does std::vector call the destructor while leaving a different scope?
[英]how does std::vector deal with the call to destructor?
當嘗試實現std::vector
時,我對對析構函數的隱式調用感到困惑。
那么向量中的元素可能是:
T
T*
, shared_ptr<T>
int
int *
調用resize()
、 reserve()
、 erase()
或pop_back()
時,可能會調用析構函數。
我想知道std::vector
如何處理對析構函數的調用。
我發現,只有當類型是內置指針時, std::vector
才會調用析構函數(當然,如果它有的話)。
std::vector
是通過區分類型並判斷是否調用析構函數來實現的嗎?
下面是關於這個問題的一些實驗:
例1 ,元素為Object。
#include <vector>
#include <iostream>
using namespace std;
struct Tmp {
~Tmp() { cerr << "Destructor is called." << endl; }
};
int main (void)
{
std::vector<Tmp>arr;
Tmp tmp = Tmp();
cerr << "Capacity:" << arr.capacity() << endl;//0
arr.push_back (tmp);
cerr << "Capacity:" << arr.capacity() << endl;//1
arr.push_back (tmp);
cerr << "Capacity:" << arr.capacity() << endl;//2
cerr << "popback start------------" << std::endl;
arr.pop_back();
arr.pop_back();
cerr << "popback end--------------" << endl;
}
output 是:
Capacity:0
Capacity:1
Destructor is called.
Capacity:2
popback start------------
Destructor is called.
Destructor is called.
popback end--------------
Destructor is called.
示例 2 ,元素是指向 obecjt 的內置指針:
...
std::vector<Tmp>arr;
Tmp * tmp = new Tmp;
...
析構函數不會被自動調用:
Capacity:0
Capacity:1
Capacity:2
popback start------------
popback end--------------
例 3 、shared_ptr
std::vector<shared_ptr<Tmp>>arr;
auto tmp = make_shared<Tmp>();
... //after being copied, the references count should be 3.
tmp = nullptr; //References count reduced by 1
cerr << "popback start------------" << std::endl;
arr.pop_back();//References count reduced by 1
arr.pop_back();//References count reduced by 1
cerr << "popback end--------------" << endl;
shared_ptr 的析構函數將被調用。 當引用減為0時,Tmp的析構函數會被調用:
Capacity:0
Capacity:1
Capacity:2
popback start------------
Destructor is called.
popback end--------------
假設您的指針定義為:
Tmp * tmp = new Tmp;
這可以這樣說明:
+--------------+ +------------+ | variable tmp | ---> | Tmp object | +--------------+ +------------+
當你有一個指針向量時:
std::vector<Tmp*> vec;
並添加一個指針:
vec.push_back(tmp);
那么你有這樣的事情:
+--------------+ | variable tmp | --\ +--------------+ | +------------+ >--> | Tmp object | +--------+ | +------------+ | vec[0] | --------/ +--------+
從這些插圖中可以很容易地看出,向量不包含Tmp
object 本身,僅包含指向它的指針。
因此,當您從向量中刪除指針時
vec.pop_back();
只有向量內部的指針被刪除和破壞。 Tmp
object 本身仍然存在,我們再次獲得第一個示例。
int*
的析構函數什么都不做。 T*
的析構函數什么也不做。
您可能認為“銷毀一個 int 指針”意味着調用delete ptr
,但這並沒有銷毀指針。 那就是破壞指針指向的內容,並回收為其分配的 memory(這是 2 個不同的步驟)。
所以vector<int*>
會破壞其中的所有int*
; 然而,這種破壞是空洞的。
vector<shared_ptr<T>>
的析構函數也銷毀其中的所有shared_ptr<T>
; 該析構函數遞減引用計數,如果它達到零則破壞T
。
vector<T>
和vector<T*>
相同——在這兩種情況下,析構函數(邏輯上)運行,但T*
的析構函數是一個 noop,而T
的析構函數是T::~T()
.
在C++中,每個實例都是一個object。一個int
是一個object,一個int*
是一個object,一個vector<int>
是一個object, struct Foo{}; Foo foo;
struct Foo{}; Foo foo;
是一個 object。
銷毀 object 有時是一個空洞。 這適用於所有原始類型,包括指針。
因為它是一個 noop,所以人們草率地談論銷毀指針就像他們談論銷毀指針指向的東西一樣。 但它們不是一回事。
struct Noisy {
~Noisy() { std::cout << "~Noisy\n"; }
};
using Ptr = Noisy*;
我可以做這個:
Ptr ptr;
ptr.~Ptr(); // compilers might complain here in non-generic code
那是一個空洞; 在這里,我試圖手動調用ptr
的(偽)析構函數,它是一個指針。 這個(偽)析構函數是一個 noop,所以沒有代碼運行。
Ptr ptr = new Noisy();
delete ptr;
這實際上破壞了*ptr
,而不是ptr
。 然后它回收我們用來存儲*ptr
的 memory。
new Noisy()
也做兩件事——它從“免費存儲”中獲取 memory 來存儲一個Noisy
,然后在它獲取的 memory 中構造 object。
您可以拆分這些操作。 您可以與創建 object 分開分配存儲(這稱為放置新),也可以與回收存儲分開銷毀 object。
這樣做在 C++ 中被認為是一種高級技術,這就是為什么沒有人與您談論它的原因。
void demo() {
alignas(Noisy) char buffer[sizeof(Noisy)];
Noisy* ptr = ::new( (void*)buffer ) Noisy();
ptr->~Noisy();
}
這會在堆棧上創建一個緩沖區(不是自動存儲),在其中手動構造一個Noisy
object,然后手動銷毀Noisy
object。
template<class T>
void demo2() {
alignas(T) char buffer[sizeof(T)];
T* ptr = ::new( (void*)buffer ) T();
ptr->~T();
}
這使得演示通用。 我可以執行demo<int>()
或demo<Noisy>()
並演示與存儲分開的構造/破壞。
std::vector
正在做這樣的事情——它管理一個存儲緩沖區(由vec.capacity()
測量)和該緩沖區“前面”的一堆對象(由vec.size()
測量)。 它使用類似於demo2
的技術手動構造和銷毀其緩沖區中的對象。
並且銷毀原始指針不會導致指向的 object 被銷毀。 但實例或智能指針並非如此。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.