这是完全有效的行为。@Cassio Neri 指出了为什么标准要求它。
短的:
" std::vector::erase(iterator position) 不一定调用相应元素的析构函数" [Op; Headline]但是调用了析构函数,处理已传输到另一个对象的相应元素的数据(通过移动构造函数到移动对象或通过 RAII 到临时实例)。
长:
为什么你不必依赖第 i 个析构函数来调用。
我将提供一些提示,为什么您根本不应该担心,在这种情况下调用了哪个析构函数。
考虑以下小班
class test
{
int * p;
public:
test (void) : p(new int[5]) { cout << "Memory " << p << " claimed." << endl; }
~test (void) { cout << "Memory " << p << " will be deleted." << endl; delete p; }
};
如果您正确处理对象移动分配,则无需担心正确调用了析构函数这一事实。
test& operator= (test && rhs)
{
cout << "Move assignment from " << rhs.p << endl;
std::swap(p, rhs.p);
return *this;
}
您的移动赋值运算符必须将“覆盖”的对象的状态转移到“移出”的对象(rhs
此处),因此它的析构函数将采取适当的行动(如果析构函数需要处理某些事情)。也许您应该使用“交换”成员函数之类的东西来为您进行转移。
如果您的对象是不可移动的,则在将新数据复制到对象之前,您必须在复制分配操作中处理已擦除对象的“清理”(或依赖于对象当前状态的任何操作)。
test& operator= (test const &rhs)
{
test tmp(rhs);
std::swap(p, tmp.p);
return *this;
}
在这里,我们使用 RAII 并再次使用swap
(它也可能仍然是一个成员函数;但 test 只有一个指针......)。的析构函数tmp
将使事情变得舒适。
让我们做一个小测试:
#include <vector>
#include <iostream>
using namespace std;
class test
{
int * p;
public:
test (void) : p(new int[5]) { cout << "Memory " << p << " claimed." << endl; }
test& operator= (test && rhs)
{
cout << "Move assignment from " << rhs.p << endl;
std::swap(p, rhs.p);
return *this;
}
~test (void) { cout << "Memory " << p << " will be deleted." << endl; delete p; }
};
int main (void)
{
cout << "Construct" << endl;
std::vector<test> v(5);
cout << "Erase" << endl;
v.erase(v.begin()+2);
cout << "Kick-off" << endl;
return 0;
}
结果是
Construct
Memory 012C9F18 claimed.
Memory 012CA0F0 claimed.
Memory 012CA2B0 claimed. // 2nd element
Memory 012CA2F0 claimed.
Memory 012CA110 claimed.
Erase
Move assignment from 012CA2F0
Move assignment from 012CA110
Memory 012CA2B0 will be deleted. // destruction of the data of 2nd element
Kick-off
Memory 012C9F18 will be deleted.
Memory 012CA0F0 will be deleted.
Memory 012CA2F0 will be deleted.
Memory 012CA110 will be deleted.
如果您的移动(或复制)分配操作将关键属性移交给将要销毁的对象,则声明的每个内存位置都将被正确释放。
如果你的赋值操作设计得当,每个依赖对象内部状态的析构函数都会被调用,并带有适当的对象。