Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Название:Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Автор:
- Жанр:
- Издательство:Литагент «ДМК»233a80b4-1212-102e-b479-a360f6b39df7
- Год:2006
- Город:Москва
- ISBN:5-94074-304-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ краткое содержание
Эта книга представляет собой перевод третьего издания американского бестселлера Effective C++ и является руководством по грамотному использованию языка C++. Она поможет сделать ваши программы более понятными, простыми в сопровождении и эффективными. Помимо материала, описывающего общую стратегию проектирования, книга включает в себя главы по программированию с применением шаблонов и по управлению ресурсами, а также множество советов, которые позволят усовершенствовать ваши программы и сделать работу более интересной и творческой. Книга также включает новый материал по принципам обработки исключений, паттернам проектирования и библиотечным средствам.
Издание ориентировано на программистов, знакомых с основами C++ и имеющих навыки его практического применения.
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
...
delete stringPtr1;
delete[]stringPtr2;
Что произойдет, если использовать форму «[]» с stringPtr1? Результат не определен, но вряд ли он будет приятным. В предположении, что память организована, как в приведенной выше схеме, delete сначала прочитает размер массива, а затем будет вызывать деструкторы, не обращая внимания на тот факт, что память, с которой он работает, не только не является массивом, но даже не содержит объектов того типа, для которых должны быть вызваны деструкторы.
Что случится, если вы не используете форму «[]» для 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 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 происходит из-за того, что исключение возникает между моментом создания ресурса и моментом помещения его в управляющий объект.
Избежать подобной проблемы просто: используйте отдельные предложения для создания объекта Widget и помещения его в интеллектуальный указатель, а затем передайте этот интеллектуальный указатель processWidgets:
std::tr1::shared_ptr pw(new Widget); // поместить новый объект
// в интеллектуальный указатель
// в отдельном предложении
processWidget(pw, priority()); // этот вызов не приведет
// к утечке
Такой способ работает потому, что компиляторам предоставляется меньше свободы в переопределении порядка операций в разных предложениях, чем в одном. В модифицированном коде выражение «new Widget» и вызов конструктора tr1::shared_ptr отделены от вызова priority, поэтому компилятор не может вставить вызов priority между ними.
• Помещайте объекты, выделенные оператором new, в «интеллектуальные» указатели в отдельном предложении. В противном случае такие вызовы могут привести к утечкам ресурсов, если возникнет исключение.
Глава 4
Проектирование программ и объявления
Проектирование программного обеспечения – это приемы получения программ, которые делают то, чего вы от них хотите. Обычно проект начинается с довольно общей идеи, но затем обрастает деталями настолько, чтобы можно было приступить к разработке конкретных интерфейсов. Интерфейсы должны затем превратиться в объявления на языке C++. В настоящей главе мы рассмотрим проблему проектирования и объявления хороших интерфейсов на C++. Начнем с одного из самых важных правил проектирования интерфейсов: использовать их правильно должно быть просто, а неправильно – трудно. Отталкиваясь от этой мысли, мы сформулируем ряд более конкретных правил, касающихся самых разных тем, а именно: корректность, эффективность, инкапсуляция, удобство сопровождения, расширяемость и следование принятым соглашениям.
Читать дальшеИнтервал:
Закладка: