2011-02-04 3 views
3

Следующий код используется только для иллюстрации моего вопроса.Явный деструктор

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 
} 

Если у меня есть array<string> arr в main.cpp. Когда я использую erase(5), объект element[5] уничтожается, но пространство element[5] не будет освобождено, могу ли я просто использовать element[5] = "abc", чтобы поместить здесь новое значение? или мне нужно использовать новое размещение для размещения нового значения в пространстве element [5]?

Когда программа заканчивается, arr<string> вызовет свой собственный деструктор, который также вызывает delete [] element. Таким образом, деструктор строки будет запущен, чтобы сначала уничтожить объект, а затем освободить пространство. Но поскольку я явно уничтожил element[5], имеет ли значение деструктор (который вызывается деструктором arr), запускается дважды, чтобы уничтожить element[5]? Я знаю, что пространство не может быть освобождено дважды, как насчет объекта? Я сделал несколько тестов и обнаружил, что это кажется прекрасным, если я просто уничтожу объект дважды, а не освобождая место дважды.

Update

Ответы:

(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.

+1

Это плохой подход. Вызов деструктора в два раза, как неопределенное поведение, из которого «кажется прекрасным» является вполне приемлемым результатом. Это не хорошо. Хотя «стирание» из массива в любом случае имеет мало смысла. – GManNickG

+1

Я согласен с @GMan: что значит «стирать» элементы из массива? Вы пытаетесь эмулировать реализацию C++ 'std :: vector'? Если да, перемещаете ли вы все элементы после стираемого элемента до «заполнить отверстие»? –

+0

@ Gman: «стереть» не имеет для меня никакого смысла. – Sean

ответ

6

Вы должны использовать размещение новый синтаксис:

new (element + 5) string("abc"); 

Неверно было бы указать element[5] = "abc"; это вызовет operator= на element[5], что не является допустимым объектом, что дает неопределенное поведение.

Когда заканчивается программа, то arr<string> будет называть свой деструктор, который также вызывает delete [] element.

Это неправильно: вы собираетесь закончить вызов деструктора для объектов, деструкторы уже называли (например, elements[5] в вышеупомянутом примере). Это также приводит к неопределенному поведению.

Рассмотрите возможность использования std::allocator и его интерфейса. Это позволяет легко отделить выделение от строительства. Он используется контейнерами стандартной библиотеки C++.

+0

Стандарт позволяет деструктору вызываться явно, если для размещения объекта используется новый объект, возвращаемый обратно в это место перед вызовом автоматического деструктора. –

+0

@Ben: Правильно, но это не похоже на то, что новые элементы строятся на месте. Трудно сказать, поскольку OP только опубликовал небольшой фрагмент, но я бы не стал предполагать, что функция «erase» также создаст новые объекты. Независимо от того, что использование «распределителя» - это способ пойти сюда: он делает все это довольно простым (ну, как ни странно, как это происходит, по крайней мере ...) –

+0

Да, но ОП задал вопрос о размещении новых , Таким образом, ответ заключается не только в том, что в этом сценарии допускается размещение нового, оно * обязательно *. –

2

Просто объяснить далее как именно UD может укусить вас ....

erase() Если у вас такой элемент - явно ссылающегося деструктор - что деструктор может делать такие вещи, как ссылочных декремент счетчиков + очистки, удаления указателей и т. д. Когда ваш массив <> деструктор затем выполняет команду delete[] element, которая будет вызывать деструкторы по каждому элементу поочередно, а для стираемых элементов эти деструкторы, скорее всего, будут повторять свое обслуживание счетчика ссылок, удаление указателя и т. д., но на этот раз начальный состояние не так, как они ожидают, и их действия могут привести к сбою программы.

По этой причине, как говорит Бен в своем комментарии к ответу Джеймса, вы абсолютно должны были заменить стертый элемент - с помощью размещения 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_ в 0 после delete. Это, вероятно, будет работать на большинстве систем, но я уверен, что что-то в стандартном высказывании, чтобы вызвать деструктор дважды, является UD независимо.

+0

@ Тони: очень хорошее описание. – Sean

Смежные вопросы