Читать «Стандарты программирования на С++. 101 правило и рекомендация» онлайн - страница 150
Андрей Александреску
• p
после выполнения delete p;
) и недействительные итераторы (например, vector<T>::iterator i
после вставки в начало контейнера, к которому обращается итератор). Заметим, что висячие указатели и недействительные итераторы концептуально идентичны, и последние часто непосредственно содержат первые. Обычно небезопасно и непредсказуемо делать что-либо с такими указателями и итераторами, за исключением присваивания другого корректного значения недействительному объекту (например, p = new Object;
или i = v.begin();
).
• reinterpret_cast
(см. рекомендацию 92) или при обращении за пределы границ массива.
Никогда не забывайте о времени жизни объекта и его корректности. Не разыменовывайте недействительные итераторы и указатели. Не делайте никаких предположений о том, что делает и чего не делает оператор delete
; освобожденная память — это освобожденная память, и обращений к ней не должно быть ни при каких условиях. Не пытайтесь играться со временем жизни объекта путем вызова деструктора вручную (например, obj.~T()
) с последующим вызовом размещающего new
(см. рекомендацию 55).
Не используйте небезопасное наследство С: strcpy
, strncpy
, sprintf
или прочие функции, которые выполняют запись в буфер без проверки выхода за его пределы. Функция strcpy
не выполняет проверки границ буфера, а функция [C99] strncpy
выполняет проверку, но не добавляет нулевой символ при выходе на границу. Обе эти функции — потенциальный источник неприятностей. Используйте современные, более безопасные и гибкие структуры и функции, такие, которые имеются в стандартной библиотеке С++ (см. рекомендацию 77). Они не всегда совершенно безопасны (в основном это связано с вопросами эффективности), но существенно меньше подвержены ошибкам и позволяют создавать гораздо более безопасный код.
Ссылки
100. Не рассматривайте массивы полиморфно
Резюме
Полиморфная работа с массивами — большая ошибка. К сожалению, обычно компилятор никак на нее не реагирует. Не попадайтесь в эту ловушку!
Обсуждение
Указатели служат одновременно для двух целей: в качестве малого размера идентификаторов объектов и в качестве итераторов для массивов (они могут обходить массивы объектов с использованием арифметики указателей). В качестве идентификаторов имеет смысл рассматривать указатель на Derived
как указатель на Base
. Однако как только мы переходим ко второй цели, такая замена не работает, поскольку массив объектов Derived
— совсем не то же, что и массив объектов Base
. Чтобы проиллюстрировать сказанное, заметим, что и мышь, и слон — оба млекопитающие, но это не значит, что колонна из ста слонов будет по длине такой же, что и колонна из ста мышей.
Размер имеет значение. При замене указателя на Derived
указателем на Base
компилятор точно знает, как следует подкорректировать (при необходимости) указатель, поскольку у него есть достаточное количество информации об обоих классах. Однако при выполнении арифметических операций над указателем p
на Base
компилятор вычисляет p[n]
как *(p+n*sizeof(Base)
), таким образом полагаясь на то, что объекты, находящиеся в памяти, — это объекты типа Base
, а не некоторого производного типа, который может иметь другой размер. Представляете, какая ерунда может получиться при работе с массивом объектов Derived
, если вы преобразуете указатель на начало этого массива в указатель типа Base*
(что компилятор вполне допускает), а затем примените арифметические операции к этому указателю (что компилятор также пропустит, не моргнув глазом)!