diff --git a/src/07_inheritance.md b/src/07_inheritance.md index 66ed037..904cbe8 100644 --- a/src/07_inheritance.md +++ b/src/07_inheritance.md @@ -451,8 +451,8 @@ base2& to_base2(derived& d) { При этом, понятно, кастовать `void*` куда-то корректно можно, если там изначально было то, куда вы кастуете. Аналогично, *вниз по иерархии* (от базового к наследуемому) можно кастовать только тогда, когда совпадает динамический тип. Иначе UB. -- `reinterpret_cast` — всё зашкварное из C-style cast'а. Перевод указателей несвязанных друг с другом типов, указателей в число и обратно. Простой и эффективный способ получить UB. В стандарте так и написано, это implementation-defined cast. Обратитесь к поставщику вашего компилятора, чтобы понять, как у вас работает `reinterpret_cast`. -- `const_cast`. Снимает модификаторы `const` и `volatile`. Чаще всего это делать не надо, но иногда бывает нужно всё-таки. В стародавние времена, когда `const`'ов не было, были функции, принимавшие указатель. Неконстантный, хотя не меняли его содержимое. И вот если вы хотите использовать эту функцию, вы можете снять `const` с указателя. А вообще правило вот какое: **если изначальный объект был `const`, снимать с него `const` ни в коем случае нельзя (UB). Если изначальный объект константным не был, а потом вы сначала навесили `const`, а потом сняли, то всё хорошо**. +- `reinterpret_cast` — всё зашкварное из C-style cast'а. Перевод указателей несвязанных друг с другом типов, указателей в число и обратно. Простой и эффективный способ получить UB, так как с помощью `reintepret_cast` очень просто нарушить `strict aliasing rule`. В стандарте так и написано, это implementation-defined cast. Обратитесь к поставщику вашего компилятора, чтобы понять, как у вас работает `reinterpret_cast`. Но как правило `reintepret_cast(T1*)` работает как `static_cast(static_cast(T1*))`. +- `const_cast`. Снимает модификаторы `const` и `volatile` (с помощью `const_cast` можно также навешивать `const`, но это не имеет особого смысла). Чаще всего это делать не надо, но иногда бывает нужно всё-таки. В стародавние времена, когда `const`'ов не было, были функции, принимавшие указатель. Неконстантный, хотя не меняли его содержимое. И вот если вы хотите использовать эту функцию, вы можете снять `const` с указателя. А вообще правило вот какое: **если изначальный объект был `const`, снимать с него `const` ни в коем случае нельзя (UB). Если изначальный объект константным не был, а потом вы сначала навесили `const`, а потом сняли, то всё хорошо**. - `dynamic_cast`. Немного другое, нежели все остальные касты. Работает для указателей и ссылок **на полиморфные (хотя бы одна виртуальная функция) типы**. Кастует по иерархии вниз (это, напомню, от базового к наследуемому), но, в отличие от `static_cast`'а, проверяет, что преобразование корректно. Если некорректно, возвращает `nullptr` в случае указателей, кидает [исключение](./08_exceptions.md) `std::bad_cast` в случае ссылок. Работает это при помощи RTTI: @@ -492,7 +492,7 @@ struct error2 : derived { ### `protected`. Представим, что мы пишем виджет на основе QT. Там есть базовый виджет, у которого есть операции, что делать в случае нажатия мышки, в случае перемещения колёсика и прочее подобное. Вам всё это нужно переопределить. В таком случае в базовом виджете используется ключевое слово `protected`. Оно для похожих случаев и было создано, лол. Это модификатор доступа, дающий доступ только дочерним классам и себе. - +Стоит отметить, что `protected` предоставляет доступ только внутри дочернего класса, то есть если есть функция в `derived` классе и мы передаем в нее объект типа `base`, то обратиться к `protected` полям и функциям `base` мы все равно не сможем. С ним, правда, есть вопрос. Если метод не ломает инвариант, почему он не `public`, а если ломает, то хотим ли мы давать доступ дочерним классам. Тем не менее, эти вопросы не риторические, если вы нашли на них ответ — делайте `protected`. ## Ещё best practices. diff --git a/src/08_exceptions.md b/src/08_exceptions.md index c8976a5..a7f36db 100644 --- a/src/08_exceptions.md +++ b/src/08_exceptions.md @@ -314,6 +314,38 @@ private: ``` Теперь из деструктора мы ничего не бросаем. Но теперь мы **обязаны явно извне вызывать `flush`, если заинтересованы в получении ошибок при закрытии**. +Рассмотрим ситуацию, когда мы хотим в конструкторе делать что-то, что может бросить исключение, тогда, если мы выделяем какие-либо ресурсы, нам необходимо оборачивать все вызываемые функции в try-catch блок, что может быть громоздко из-за большого количества выделяемых ресурсов (так как конструктор не завершил свое исполнение, в случае исключения деструктор вызван не будет). Для данных целей может быть использован **делегирующий конструктор**. +```c++ +struct file_descriptor { +private: + File(FILE * file) : fd(file){} + void init_work_with_file() {} // can throw an exception +public: + file_descriptor(const char* filename, const char* mode) + : File(fopen(filename, mode)) + { + init_work_with_file(); + } + + void flush() { + int result = close(fd); + if (result != 0) + throw std::runtime_exception("File closing failed."); + fd = -1; + } + + ~file_descriptor() { + if (fd != -1) + close(fd); + } + +private: + FILE * fd; +}; +``` + +В данном примере, если в публичном конструкторе будет выброшено исключение, то утечек данных не произойдет, так как приватный конструктор уже закончит свое исполнение к моменту выброса исключения и будет вызван деструктор, который высвободит выделенные ресурсы. + ## Гарантии исключений. Уважаемые знатоки, внимание, вопрос. Через одну минуту найдите ошибку в следующем операторе присваивания для строк: ```c++