Правило простое: если вы используете [] в выражении new, то должны использовать [] и в соответствующем выражении delete. Если вы не используете [] в new, то не надо использовать его в соответствующем выражении delete.
Это правило особенно важно помнить при написании классов, содержащих указатели на динамически распределенную память, в которых есть несколько конструкторов, поскольку в этом случае вы должны использовать одинаковую форму new во всех конструкторах для инициализации членов-указателей. Если этого не сделать, то как узнать, какую форму delete применить в деструкторе?
Данное правило для тех, кто часто прибегает к использованию typedef, поскольку из него следует, что автор typedef должен документировать, какую форму delete применять для удаления объектов типа, описываемого typedef. Рассмотрим пример:
typedef std::string AddressLines[5]; // адрес человека состоит из 4 строк,
// каждая из которых имеет тип string
Поскольку AddressLines – массив, то следующему применению new
std::string *pal = new AddressLines; // отметим, что “new AddressLines”
// вернет string *, как и
// выражение “new string[4]”
должна соответствовать форма delete для массивов:
delete pal; // не определено!
delete[] pal; // правильно
Чтобы избежать путаницы, старайтесь не примененять typedef для определения типов массивов. Это просто, потому что стандартная библиотека C++ (см. правило 54) включает шаблонные классы string и vector, позволяющие практически полностью избавиться от динамических массивов. Так, в примере выше AddressLines можно было бы определить как вектор строк: vector.
Что следует помнить
• Если вы используете [] в выражении new, то должны применять [] и в соответствующем выражении delete. Если вы не используете квадратные скобки [] в выражении new, то не должны использовать их и в соответствующем выражении delete.
Правило 17: Помещение в «интеллектуальный» указатель объекта, вьщеленного с помощью new, лучше располагать в отдельном предложении
Предположим, что есть функция, возвращающая уровень приоритета обработки, и другая функция для выполнения некоторой обработки динамически выделенного объекта Widget в соответствии с этим приоритетом:
int priority();
void processWidgets(std::tr1::shared_ptr pw, int priority);
Помня о премудростях применения объектов, управляющих ресурсами (см. правило 13), processWidgets использует «интеллектуальный» указатель (здесь – tr1::shared_ptr) для обработки динамически выделенного объекта. Рассмотрим теперь такой вызов processWidgets:
processWidgets(new Widget, priority());
Стоп, не надо его рассматривать! Он не скомпилируется. Конструктор tr1::shared_ptr, принимающий указатель, объявлен с ключевым словом explicit, поэтому не происходит неявного преобразования из типа указателя, возвращенного выражением «new Widget», в тип tr1::shared_ptr, которого ожидает функция process-Widgets. Однако следующий код компилируется:
processWidgets(std::tr1::shared_ptr(new Widget), priority());
Как это ни странно, но несмотря на использование управляющего ресурсами объекта, здесь возможна утечка ресурсов. Разберемся, почему.
Прежде чем компилятор сможет сгенерировать вызов processWidgets, он должен вычислить аргументы, переданные ему в качестве параметров. Второй аргумент – просто вызов функции priority, но первый – (std::tr1::shared_ptr (new Widget)) – состоит из двух частей:
• выполнение выражения «new Widget»;
• вызов конструктора tr1::shared_ptr.
Перед тем как произойдет вызов processWidgets, компилятор должен сгенерировать код для решения следующих трех задач:
• вызов priority;
• выполнение «new Widget»;
• вызов конструктора tr1::shared_ptr.
Компиляторам C++ предоставлена определенная свобода в определении порядка выполнения этих операций. (И этим C++ отличается от таких языков, как Java и C#, где параметры функций всегда вычисляются в определенном порядке.) Выражение «new Widget» должно быть выполнено перед вызовом конструктора tr1::shared_ptr, потому что результат этого выражения передается конструктору в качестве аргумента, однако вызов priority может быть выполнен первым, вторым или третьим. Если компилятор решит поставить его на второе место (иногда это позволяет сгенерировать более эффективный код), то мы получим следующую последовательность операций:
1. Выполнение «new Widget».
2. Вызов priority.
3. Вызов конструктора tr1::shared_ptr.
Посмотрим, что случится, если вызов priority возбудит исключение. В этом случае указатель, возвращенный «new Widget», будет потерян, то есть не помещен в объект tr1::shared_ptr, который, как ожидается, должен предотвратить утечку ресурса. Утечка при вызове processWidgets происходит из-за того, что исключение возникает между моментом создания ресурса и моментом помещения его в управляющий объект.
Читать дальше
Конец ознакомительного отрывка
Купить книгу