以下代码仅用于说明我的问题。
template<class T>
class array<T>
{
public:
// constructor
array(cap = 10):capacity(cap)
{element = new T [capacity]; size =0;}
// destructor
~array(){delete [] element;}
void erase(int i);
private:
T *element;
int capacity;
int size;
};
template<class T>
void class array<T>::erase(int i){
// copy
// destruct object
element[i].~T(); ////
// other codes
}
如果我在main.cpp中有array<string> arr
。当我使用erase(5)
时,element[5]
的对象被销毁但element[5]
的空格不会被取消分配,我可以使用element[5] = "abc"
在此处添加新值吗?或者我是否必须使用placement new来在element [5]
的空格中添加新值?
当程序结束时,arr<string>
将调用自己的析构函数,该析构函数也调用delete [] element
。因此,字符串的析构函数将首先运行以销毁对象,然后释放空间。但是因为我明确地破坏了element[5]
,所以析构函数(由arr的destuctor调用)运行两次来破坏element[5]
吗?我知道空间不能被解除分配两次,对象怎么样?我做了一些测试,发现如果我只是两次破坏对象而不是两次释放空间似乎没问题。
的更新 的
答案是:
(1)I have to use placement new if I explicitly call destructor.
(2) repeatedly destructing object is defined as undefined behavior which may be accepted in most systems but should try to avoid this practice.
答案 0 :(得分:6)
您需要使用placement-new语法:
new (element + 5) string("abc");
说element[5] = "abc"
是不正确的;这会在operator=
上调用element[5]
,这不是一个有效的对象,会产生未定义的行为。
当程序结束时,
arr<string>
将调用自己的析构函数,该析构函数也会调用delete [] element
。
这是错误的:您将最终调用析构函数来查找已经调用了析构函数的对象(例如,在上述示例中为elements[5]
)。这也会产生不确定的行为。
考虑使用std::allocator
及其界面。它允许您轻松地将分配与构造分开。它由C ++标准库容器使用。
答案 1 :(得分:2)
进一步详细解释UD如何咬你......
如果你erase()
一个元素 - 显式调用析构函数 - 析构函数可能会做一些事情,比如减少引用计数器+清理,删除指针等等。当你的数组&lt;&gt;析构函数然后执行delete[] element
,它将依次调用每个元素上的析构函数,对于已擦除的元素,这些析构函数可能会重复其引用计数维护,指针删除等,但这次初始状态不是正如他们所期望的那样,他们的行为可能会使该计划失败。
出于这个原因,正如Ben在评论James的回答中所说,你绝对必须在调用数组的析构函数之前替换一个已擦除的元素 - 使用placement new,因此析构函数将具有一些合法的状态可以从中破坏
说明此问题的最简单类型的T
是:
struct T
{
T() : p_(new int) { }
~T() { delete p_; }
int* p_;
};
此处,p_
设置的new
值将在erase()
期间删除,如果在~array()
运行时保持不变。要解决此问题,必须将p_
更改为delete
之前~array()
有效的内容 - 通过某种方式将其清除为0或new
返回的另一个指针。最明智的方法是通过放置new
,它将构造一个新对象,为p_
获取一个新的有效值,覆盖旧的和无用的内存内容。
也就是说,您可能认为可以构造重复析构函数安全的类型:例如,通过在p_
之后将delete
设置为0。这可能适用于大多数系统,但我很确定标准中有一些东西说无论如何都要调用析构函数两次。