Удаление элемента из вектора указателей в C ++ - PullRequest
1 голос
/ 16 июня 2010

Я помню, как слышал, что следующий код не совместим с C ++, и надеялся, что кто-то с гораздо большим языком C ++, чем я, сможет подтвердить или опровергнуть его.чтобы вектор содержал указатели на недопустимую память.Теперь, очевидно, мой пример скомпилируется, и он будет работать даже на всех известных мне компиляторах, но совместим ли он со стандартом C ++ или я должен сделать следующее, что, как мне сказали, на самом деле является стандартным подходом:

while(!intList.empty()) {
  int* element = intList.back();
  intList.pop_back();
  delete element;
}

Ответы [ 8 ]

8 голосов
/ 16 июня 2010

Ваш код действителен, но лучшим решением будет использование умных указателей.

Дело в том, что все требования к std::vector находятся в разделе 23.2.4 стандарта C ++. Нет никаких ограничений относительно недействительных указателей. std::vector работает с int* как и с любым другим типом (мы не рассматриваем случай vector<bool>), ему все равно, на что они указывают.

5 голосов
/ 16 июня 2010

Ваш код в порядке. Если по какой-то причине вы беспокоитесь о недействительности элементов, измените тело цикла на

int* tmp = 0;
swap (tmp, *i);
delete tmp;
2 голосов
/ 16 июня 2010

Философия C ++ заключается в том, чтобы позволить программисту как можно больше возможностей и запрещать только те вещи, которые действительно могут причинить вред.Неверные указатели сами по себе не причиняют вреда, поэтому вы можете свободно их использовать.Что может нанести вред, так это использование указателя любым способом, что вызывает неопределенное поведение.

1 голос
/ 16 июня 2010

В конечном счете, это вопрос личного вкуса больше всего на свете. Наличие вектора, содержащего недопустимые указатели, не соответствует стандартам Ваш последний пример гарантирует, что ваш вектор никогда не будет содержать неверный указатель, так что это самый безопасный выбор.

Но если бы вы знали , что вектор никогда не будет использоваться во время цикла вашего предыдущего примера (например, если вектор имеет локальную область действия), это прекрасно.

0 голосов
/ 16 июня 2010

вектор не может содержать указатели на недопустимую память

Вот что Стандарт должен сказать о содержимом контейнера:

(23,3): Тип объектов, хранящихся в этих компонентах, должен соответствовать требованиям CopyConstructible типов (20.1.3) и дополнительным требованиям Назначаемых типов.

(20.1.3.1, CopyConstructible): В следующей Таблице 30 T - это тип, который должен быть предоставлен программой C + +, создающей экземпляр шаблона, t - это значение типа T, а u - это значение типа const T.

expression  return type  requirement
xxxxxxxxxx    xxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
T(t)                       t is equivelant to T(t)
T(u)                       u is equivelant to T(u)
t.~T()      
&t          T*           denotes the address of t
&u          const T*     denotes the address of u

(23.1.4, Assignable): 64, T - это тип, используемый для создания экземпляра контейнера, t - это значение T, а u - это значение (возможно, const) T.

expression  return type  requirement
xxxxxxxxxx    xxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
t = u         T&           t is equivilant to u

Это все, что говорится о содержании коллекции STL.В нем ничего не говорится об указателях, и в нем особо ничего не говорится о указателях, указывающих на действительную память.

Следовательно, delete указатели в vector, в то время как, скорее всего, очень плохое архитектурное решение и приглашение к болии страдания от отладчика в 3:00 утра в субботу вечером совершенно законны.

РЕДАКТИРОВАТЬ:

Относительно комментария Кранара о том, что «назначение указателя на недопустимое значение указателя приводит к неопределенному поведению«.Нет, это неверноЭтот код совершенно действителен:

Foo* foo = new Foo();
delete foo;
Foo* foo_2 = foo;  // This is legal

Недопустимо пытаться что-то сделать с этим указателем (или foo, если на то пошло):

delete foo_2; // UB
foo_2->do_something(); // UB
Foo& foo_ref = *foo_2; // UB

Просто создать дикий символУказатель является законным в соответствии со Стандартом.Вероятно, не очень хорошая идея, но, тем не менее, законная.

EDIT2:

Больше из Стандарта относительно типов указателей.

Так говорит Стандарт (3.9.2.3):

... Допустимое значение типа указателя объекта представляет собой адрес байта в памяти (1.7) илинулевой указатель (4.10) ...

... и относительно "байта в памяти" (1.7.1):

Основная единица хранения вМодель памяти C ++ - это байт.Байт, по крайней мере, достаточно большой, чтобы содержать любой элемент базового набора символов выполнения, и состоит из непрерывной последовательности битов, число которых определяется реализацией.Младший значащий бит называется младшим битом;самый значимый бит называется старшим битом.Память, доступная для программы C ++, состоит из одной или нескольких последовательностей непрерывных байтов.Каждый байт имеет уникальный адрес.

Здесь нет ничего о том, что этот байт является частью живого Foo, о том, что у вас есть доступ к нему или что-то в этом роде.Это просто байт в памяти.

0 голосов
/ 16 июня 2010

Я не верю, что это проблема соответствия стандартам.Стандарты C ++ определяют синтаксис языка и требования к реализации.Вы используете STL, которая является мощной библиотекой, но, как и все библиотеки, она не является частью самого C ++ ... хотя, я думаю, можно утверждать, что при агрессивном использовании такие библиотеки, как STL и Qt, расширяют язык на другой язык надмножества.

Недопустимые указатели полностью соответствуют стандартам C ++, компьютеру просто не понравится, когда вы разыграете их.

То, что вы спрашиваете, - это вопрос передового опыта.Если ваш код многопоточный и intList потенциально является общим, тогда ваш первый подход может быть более опасным, но, как предположил Грег, если вы знаете, что к intList невозможно получить доступ, тогда первый подход может быть более эффективным.Тем не менее, я считаю, что безопасность обычно должна выигрывать в компромиссе до тех пор, пока вы не узнаете, что существует проблема с производительностью.

Как предполагает концепция Проектирования по контракту, весь код определяет контракт, будь то явный или неявный.Настоящая проблема с таким кодом заключается в том, что вы обещаете пользователю: предварительные условия, постусловия, инварианты и т. Д. Библиотеки заключают определенный контракт, и каждая функция, которую вы пишете, определяет свой собственный контракт.Вам просто нужно выбрать подходящий баланс для вашего кода, и пока вы ясно даете понять пользователю (или себе через шесть месяцев), что безопасно, а что нет, все будет хорошо.

Если есть лучшие практики, документированные с помощью API, используйте их, когда это возможно.Они, вероятно, являются лучшими практиками по причине.Но помните, лучшая практика может быть в глазах смотрящего ... то есть они не могут быть лучшей практикой во всех ситуациях.

0 голосов
/ 16 июня 2010

При хранении сырых указателей в контейнере (я бы не рекомендовал это), а затем при необходимости двухэтапного удаления, я бы выбрал ваш первый вариант вместо второго.

Я считаю, что container :: clear () удалит содержимое карты более эффективно, чем извлечение одного элемента за раз.

Возможно, вы могли бы превратить цикл for в хороший (psuedo) forall(begin(),end(),delete) и сделать его более универсальным, чтобы он недаже не имеет значения, если вы перешли с вектора на другой контейнер.

0 голосов
/ 16 июня 2010

Где ты это услышал? Учтите это:

std::vector<int *> intList(5);

Я только что создал вектор, заполненный 5 неверными указателями.

...