提问人:sixsixqaq 提问时间:11/29/2022 最后编辑:sixsixqaq 更新时间:11/30/2022 访问量:480
std::vector 如何处理对析构函数的调用?
how does std::vector deal with the call to destructor?
问:
当 tring 实现时,我对析构函数的隐式调用感到困惑。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;
}
输出为:
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
因此,当您从向量中删除指针时
vec.pop_back();
只有向量内部的指针被删除和销毁。物体本身仍然存在,我们再次有了第一个插图。Tmp
评论
删除
对象,然后再从向量中删除指针。或者在管理生存期的地方使用智能指针。
delete
的析构函数不执行任何操作。的析构函数不执行任何操作。int*
T*
您可能认为“destroy an int pointer”的意思是调用,但这并不是破坏指针。即销毁指针指向的内容,并回收为其分配的内存(这是 2 个不同的步骤)。delete ptr
所以 a 摧毁了其中的所有 s;然而,这种破坏是一个努普。vector<int*>
int*
的破坏者也摧毁了其中的所有东西;析构函数递减引用计数,如果达到零,则销毁 .vector<shared_ptr<T>>
shared_ptr<T>
T
和 也是一样的——在这两种情况下,析构函数(逻辑上)是运行的,但 的析构函数是 noop,而析构函数是 。vector<T>
vector<T*>
T*
T
T::~T()
在 C++ 中,每个实例都是一个对象。一个是对象,一个是对象,一个是对象,是一个对象。int
int*
vector<int>
struct Foo{}; Foo foo;
摧毁一个物体有时是一个麻烦。所有基元类型(包括指针)都是如此。
因为它是一个noop,所以人们会变得草率,谈论销毁指针,就像他们谈论销毁指针所指向的内容一样。但它们不是一回事。
struct Noisy {
~Noisy() { std::cout << "~Noisy\n"; }
};
using Ptr = Noisy*;
我可以这样做:
Ptr ptr;
ptr.~Ptr(); // compilers might complain here in non-generic code
这是一个noop;在这里,我尝试手动调用 的(伪)析构函数,这是一个指针。这个(伪)析构函数是一个 noop,因此不会运行任何代码。ptr
Ptr ptr = new Noisy();
delete ptr;
这实际上破坏了,而不是。然后它会回收我们用来存储的内存。*ptr
ptr
*ptr
new Noisy()
还做了两件事——它从“自由存储”中获取内存来存储 A,然后在它得到的内存中构造该对象。Noisy
您可以拆分这些操作。您可以将存储与创建对象分开分配(这称为“放置新”),也可以将销毁对象与回收存储分开。
这样做被认为是C++中的一种高级技术,这就是为什么没有人和你谈论它的原因。
void demo() {
alignas(Noisy) char buffer[sizeof(Noisy)];
Noisy* ptr = ::new( (void*)buffer ) Noisy();
ptr->~Noisy();
}
这会在堆栈上创建一个缓冲区(而不是自动存储),在其中手动构造一个对象,然后手动销毁该对象。Noisy
Noisy
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
销毁原始指针不会导致销毁指向的对象。但对于实例或智能指针来说,情况并非如此。
评论
6
4
4
2
std::vector