» » » » Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

На нашем литературном портале можно бесплатно читать книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп, Бьёрн Страуструп . Жанр: Программирование. Онлайн библиотека дает возможность прочитать весь текст и даже без регистрации и СМС подтверждения на нашем литературном портале litmir.org.
Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп
Название: Программирование. Принципы и практика использования C++ Исправленное издание
Дата добавления: 22 август 2024
Количество просмотров: 99
Читать онлайн

Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних просмотр данного контента СТРОГО ЗАПРЕЩЕН! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту readbookfedya@gmail.com для удаления материала

Программирование. Принципы и практика использования C++ Исправленное издание читать книгу онлайн

Программирование. Принципы и практика использования C++ Исправленное издание - читать бесплатно онлайн , автор Бьёрн Страуструп

Специальное издание самой читаемой и содержащей наиболее достоверные сведения книги по C++. Книга написана Бьярне Страуструпом — автором языка программирования C++ — и является каноническим изложением возможностей этого языка.
Помимо подробного описания собственно языка, на страницах книги вы найдете доказавшие свою эффективность подходы к решению разнообразных задач проектирования и программирования. Многочисленные примеры демонстрируют как хороший стиль программирования на С-совместимом ядре C++, так и современный -ориентированный подход к созданию программных продуктов. Третье издание бестселлера было существенно переработано автором. Результатом этой переработки стала большая доступность книги для новичков. В то же время, текст обогатился сведениями и методиками программирования, которые могут оказаться полезными даже для многоопытных специалистов по C++. Не обойдены вниманием и нововведения языка: стандартная библиотека шаблонов (STL), пространства имен (namespaces), механизм идентификации типов во время выполнения (RTTI), явные приведения типов (cast-операторы) и другие.
Настоящее специальное издание отличается от третьего добавлением двух новых приложений (посвященных локализации и безопасной обработке исключений средствами стандартной библиотеки), довольно многочисленными уточнениями в остальном тексте, а также исправлением множества опечаток.
Книга адресована программистам, использующим в своей повседневной работе C++. Она также будет полезна преподавателям, студентам и всем, кто хочет ознакомиться с описанием языка «из первых рук».

Перейти на страницу:
использует оператор new явным образом и присваивает результирующий указатель на локальную переменную, создавая очень опасную ситуацию.

19.5.1. Потенциальные проблемы управления ресурсами

 

 Рассмотрим одну из опасностей, таящуюся в следующем, казалось бы, безвредном присваивании указателей:

int* p = new int[s]; // занимаем память

Она заключается в трудности проверки того, что данному оператору new соответствует оператор delete. В функции suspicious() есть инструкция delete[] p, которая могла бы освободить память, но представим себе несколько причин, по которым это может и не произойти. Какие инструкции можно было бы вставить в часть, отмеченную многоточием, ..., чтобы вызвать утечку памяти? Примеры, которые мы подобрали для иллюстрации возникающих проблем, должны натолкнуть вас на размышления и вызвать подозрения относительно такого кода. Кроме того, благодаря этим примерам вы оцените простоту и мощь альтернативного решения.

Возможно, указатель p больше не ссылается на объект, который мы хотим уничтожить с помощью оператора delete.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

  // ...

  if (x) p = q;        // устанавливаем указатель p на другой объект

  // ...

  delete[] p;          // освобождаем память

}

Мы включили в программу инструкцию if (x), чтобы гарантировать, что вы не будете знать заранее, изменилось ли значение указателя p или нет. Возможно, программа никогда не выполнит оператор delete.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

  // ...

  if (x) return;

  // ...

  delete[] p; // освобождаем память

}

Возможно, программа никогда не выполнит оператор delete, потому что сгенерирует исключение.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

  vector<int> v;

  // ...

  if (x) p[x] = v.at(x);

  // ...

  delete[] p;          // освобождаем память

}

 

 Последняя возможность беспокоит нас больше всего. Когда люди впервые сталкиваются с такой проблемой, они считают, что она связана с исключениями, а не с управлением ресурсами. Не понимая истинных причин проблемы, они пытаются перехватывать исключения.

void suspicious(int s, int x) // плохой код

{

  int* p = new int[s]; // занимаем память

  vector<int> v;

  // ...

  try {

    if (x) p[x] = v.at(x);

    // ...

  } catch (...) {      // перехватываем все исключения

  delete[] p;          // освобождаем память

  throw;               // генерируем исключение повторно

  }

  // ...

  delete[] p;          // освобождаем память

}

Этот код решает проблему за счет дополнительных инструкций и дублирования кода, освобождающего ресурсы (в данном случае инструкции delete[] p;). Иначе говоря, это некрасивое решение; что еще хуже — его сложно обобщить. Представим, что мы задействовали несколько ресурсов.

void suspicious(vector<int>& v, int s)

{

  int* p = new int[s];

  vector<int>v1;

  // ...

  int* q = new int[s];

  vector<double> v2;

  // ...

  delete[] p;

  delete[] q;

}

Обратите внимание на то, что, если оператор new не сможет выделить свободную память, он сгенерирует стандартное исключение bad_alloc. Прием try ... catch в этом примере также успешно работает, но нам потребуется несколько блоков try, и код станет повторяющимся и ужасным. Мы не любим повторяющиеся и запутанные программы, потому что повторяющийся код сложно сопровождать, а запутанный код не только сложно сопровождать, но и вообще трудно понять. 

ПОПРОБУЙТЕ

Добавьте блоки try в последний пример и убедитесь, что все ресурсы будут правильно освобождаться при любых исключениях.

19.5.2. Получение ресурсов — это инициализация

 К счастью, нам не обязательно копировать инструкции try...catch, чтобы предотвратить утечку ресурсов. Рассмотрим следующий пример:

void f(vector<int>& v, int s)

{

  vector<int> p(s);

  vector<int> q(s);

  // ...

 

 Это уже лучше. Что еще более важно, это очевидно лучше. Ресурс (в данном случае свободная память) занимается конструктором и освобождается соответствующим деструктором. Теперь мы действительно решили нашу конкретную задачу, связанную с исключениями. Это решение носит универсальный характер; его можно применить ко всем видам ресурсов: конструктор получает ресурсы для объекта, который ими управляет, а соответствующий деструктор их возвращает. Такой подход лучше всего зарекомендовал себя при работе с блокировками баз данных (database locks), сокетами (sockets) и буферами ввода-вывода (I/O buffers) (эту работу делают объекты класса iostream). Соответствующий принцип обычно формулируется довольно неуклюже: “Получение ресурса есть инициализация” (“Resource Acquisition Is Initialization” — RAII).

Рассмотрим предыдущий пример. Как только мы выйдем из функции f(), будут вызваны деструкторы векторов p и q: поскольку переменные p и q не являются указателями, мы не можем присвоить им новые значения, инструкция return не может предотвратить вызов деструкторов и никакие исключения не генерируются.

Это универсальное правило: когда поток управления покидает область видимости, вызываются деструкторы для каждого полностью созданного объекта и активизированного подобъекта. Объект считается полностью созданным, если его конструктор закончил свою работу. Исследование всех следствий, вытекающих из этих двух утверждений, может вызвать головную боль. Будем считать просто, что конструкторы и деструкторы вызываются, когда надо и где надо.

 

 В частности, если хотите выделить в области видимости свободную память переменного размера, мы рекомендуем использовать класс vector, а не “голые” операторы new и delete. 

19.5.3. Гарантии

Что делать, если вектор невозможно ограничить только одной областью (или подобластью) видимости? Рассмотрим пример.

vector<int>* make_vec() // создает заполненный вектор

{

  vector<int>* p = new vector<int>; // выделяем свободную память

  // ...заполняем вектор данными;

  // возможна генерация исключения...

  return p;

}

Это довольно распространенный пример: мы вызываем функцию, чтобы создать сложную структуру данных, и возвращаем эту структуру как результат. Однако, если при заполнении вектора возникнет исключение, функция make_vec() потеряет этот объект класса vector.

Перейти на страницу:
Комментариев (0)