Ссылки
[Boost] • [Cowan01] • [Murray93] §2.6 • [Sutter04] §2-3 • [Tsai01]
99. Не используйте недействительные объекты и небезопасные функции
Резюме
Вы же не используете просроченные лекарства? И недействительные объекты, и "антикварные", но небезопасные функции способны навредить здоровью ваших программ.
Обсуждение
Имеется три основные категории недействительных объектов.
• Уничтоженные объекты. Типичными примерами таких объектов являются автоматические объекты, вышедшие из области видимости, и удаленные динамические объекты. После того как вы вызвали деструктор объекта, его время жизни истекло, и любые действия с ним небезопасны и приводят к непредсказуемым последствиям.
• Семантически недействительные объекты. Типичные примеры включают "висячие" указатели на удаленные объекты (например, указатель p
после выполнения delete p;
) и недействительные итераторы (например, vector::iterator i
после вставки в начало контейнера, к которому обращается итератор). Заметим, что висячие указатели и недействительные итераторы концептуально идентичны, и последние часто непосредственно содержат первые. Обычно небезопасно и непредсказуемо делать что-либо с такими указателями и итераторами, за исключением присваивания другого корректного значения недействительному объекту (например, p = new Object;
или i = v.begin();
).
• Объекты, которые никогда не были действительными. Примеры включают объекты, "полученные" путем преобразования указателя с использованием reinterpret_cast
(см. рекомендацию 92) или при обращении за пределы границ массива.
Никогда не забывайте о времени жизни объекта и его корректности. Не разыменовывайте недействительные итераторы и указатели. Не делайте никаких предположений о том, что делает и чего не делает оператор delete
; освобожденная память — это освобожденная память, и обращений к ней не должно быть ни при каких условиях. Не пытайтесь играться со временем жизни объекта путем вызова деструктора вручную (например, obj.~T()
) с последующим вызовом размещающего new
(см. рекомендацию 55).
Не используйте небезопасное наследство С: strcpy
, strncpy
, sprintf
или прочие функции, которые выполняют запись в буфер без проверки выхода за его пределы. Функция strcpy
не выполняет проверки границ буфера, а функция [C99] strncpy
выполняет проверку, но не добавляет нулевой символ при выходе на границу. Обе эти функции — потенциальный источник неприятностей. Используйте современные, более безопасные и гибкие структуры и функции, такие, которые имеются в стандартной библиотеке С++ (см. рекомендацию 77). Они не всегда совершенно безопасны (в основном это связано с вопросами эффективности), но существенно меньше подвержены ошибкам и позволяют создавать гораздо более безопасный код.
Ссылки
[C99] • [Sutter00] §1 • [Sutter04] §2-3
100. Не рассматривайте массивы полиморфно
Резюме
Полиморфная работа с массивами — большая ошибка. К сожалению, обычно компилятор никак на нее не реагирует. Не попадайтесь в эту ловушку!
Обсуждение
Указатели служат одновременно для двух целей: в качестве малого размера идентификаторов объектов и в качестве итераторов для массивов (они могут обходить массивы объектов с использованием арифметики указателей). В качестве идентификаторов имеет смысл рассматривать указатель на Derived
как указатель на Base
. Однако как только мы переходим ко второй цели, такая замена не работает, поскольку массив объектов Derived
— совсем не то же, что и массив объектов Base
. Чтобы проиллюстрировать сказанное, заметим, что и мышь, и слон — оба млекопитающие, но это не значит, что колонна из ста слонов будет по длине такой же, что и колонна из ста мышей.
Размер имеет значение. При замене указателя на Derived
указателем на Base
компилятор точно знает, как следует подкорректировать (при необходимости) указатель, поскольку у него есть достаточное количество информации об обоих классах. Однако при выполнении арифметических операций над указателем p
на Base
компилятор вычисляет p[n]
как *(p+n*sizeof(Base)
), таким образом полагаясь на то, что объекты, находящиеся в памяти, — это объекты типа Base
, а не некоторого производного типа, который может иметь другой размер. Представляете, какая ерунда может получиться при работе с массивом объектов Derived
, если вы преобразуете указатель на начало этого массива в указатель типа Base*
(что компилятор вполне допускает), а затем примените арифметические операции к этому указателю (что компилятор также пропустит, не моргнув глазом)!
Читать дальше