Что случится, если вы не используете форму «[]» для stringPtr2? Неизвестно, но можно предположить, что будет вызван только один деструктор, хотя нужно было вызвать несколько. Более того, это не определено даже для встроенных типов, подобных int, несмотря на то что у них нет деструкторов.
Правило простое: если вы используете [] в выражении 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
Помня о премудростях применения объектов, управляющих ресурсами (см. правило 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
Как это ни странно, но несмотря на использование управляющего ресурсами объекта, здесь возможна утечка ресурсов. Разберемся, почему.
Прежде чем компилятор сможет сгенерировать вызов processWidgets, он должен вычислить аргументы, переданные ему в качестве параметров. Второй аргумент – просто вызов функции priority, но первый – (std::tr1::shared_ptr
• выполнение выражения «new Widget»;
• вызов конструктора tr1::shared_ptr.
Перед тем как произойдет вызов processWidgets, компилятор должен сгенерировать код для решения следующих трех задач:
• вызов priority;
• выполнение «new Widget»;
• вызов конструктора tr1::shared_ptr.