Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/07_inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T2*>(T1*)` работает как `static_cast<T2*>(static_cast<void*>(T1*))`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вообще-то reinterpret_cast из указателя в указатель именно так и определён в стандарте, не надо обращаться ни к каким поставщикам компиляторов (implementation-defined другие версии reinterpret_cast'а, которые здесь не упомянуты, например из указателя в число)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему strict aliasing rule отформатирован как код?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

после "Но как правило" должна быть запятая (если это выражение останется после изменений)

- `const_cast`. Снимает модификаторы `const` и `volatile` (с помощью `const_cast` можно также навешивать `const`, но это не имеет особого смысла). Чаще всего это делать не надо, но иногда бывает нужно всё-таки. В стародавние времена, когда `const`'ов не было, были функции, принимавшие указатель. Неконстантный, хотя не меняли его содержимое. И вот если вы хотите использовать эту функцию, вы можете снять `const` с указателя. А вообще правило вот какое: **если изначальный объект был `const`, снимать с него `const` ни в коем случае нельзя (UB). Если изначальный объект константным не был, а потом вы сначала навесили `const`, а потом сняли, то всё хорошо**.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

дописанное в скобках относится не только к const, не вижу смысла уточнять

Suggested change
- `const_cast`. Снимает модификаторы `const` и `volatile` (с помощью `const_cast` можно также навешивать `const`, но это не имеет особого смысла). Чаще всего это делать не надо, но иногда бывает нужно всё-таки. В стародавние времена, когда `const`'ов не было, были функции, принимавшие указатель. Неконстантный, хотя не меняли его содержимое. И вот если вы хотите использовать эту функцию, вы можете снять `const` с указателя. А вообще правило вот какое: **если изначальный объект был `const`, снимать с него `const` ни в коем случае нельзя (UB). Если изначальный объект константным не был, а потом вы сначала навесили `const`, а потом сняли, то всё хорошо**.
- `const_cast`. Снимает модификаторы `const` и `volatile` (может и навешивать их, но это не имеет особого смысла). Чаще всего это делать не надо, но иногда бывает нужно всё-таки. В стародавние времена, когда `const`'ов не было, были функции, принимавшие указатель. Неконстантный, хотя не меняли его содержимое. И вот если вы хотите использовать эту функцию, вы можете снять `const` с указателя. А вообще правило вот какое: **если изначальный объект был `const`, снимать с него `const` ни в коем случае нельзя (UB). Если изначальный объект константным не был, а потом вы сначала навесили `const`, а потом сняли, то всё хорошо**.

- `dynamic_cast`. Немного другое, нежели все остальные касты. Работает для указателей и ссылок **на полиморфные (хотя бы одна виртуальная функция) типы**. Кастует по иерархии вниз (это, напомню, от базового к наследуемому), но, в отличие от `static_cast`'а, проверяет, что преобразование корректно. Если некорректно, возвращает `nullptr` в случае
указателей, кидает [исключение](./08_exceptions.md) `std::bad_cast` в случае ссылок. Работает это при помощи RTTI:

Expand Down Expand Up @@ -492,7 +492,7 @@ struct error2 : derived {

### `protected`.
Представим, что мы пишем виджет на основе QT. Там есть базовый виджет, у которого есть операции, что делать в случае нажатия мышки, в случае перемещения колёсика и прочее подобное. Вам всё это нужно переопределить. В таком случае в базовом виджете используется ключевое слово `protected`. Оно для похожих случаев и было создано, лол. Это модификатор доступа, дающий доступ только дочерним классам и себе.

Стоит отметить, что `protected` предоставляет доступ только внутри дочернего класса, то есть если есть функция в `derived` классе и мы передаем в нее объект типа `base`, то обратиться к `protected` полям и функциям `base` мы все равно не сможем.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я понимаю, что ты хочешь донести, но сформулировано непонятно, что значит "внутри"? вопрос актуален даже если забить на то, что у derived тоже могут быть наследники, а если не забивать, стоит сформулировать ещё точнее

С ним, правда, есть вопрос. Если метод не ломает инвариант, почему он не `public`, а если ломает, то хотим ли мы давать доступ дочерним классам. Тем не менее, эти вопросы не риторические, если вы нашли на них ответ — делайте `protected`.
Comment on lines +495 to 496
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

стоит разделить эти абзацы


## Ещё best practices.
Expand Down
32 changes: 32 additions & 0 deletions src/08_exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
```

В данном примере, если в публичном конструкторе будет выброшено исключение, то утечек данных не произойдет, так как приватный конструктор уже закончит свое исполнение к моменту выброса исключения и будет вызван деструктор, который высвободит выделенные ресурсы.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

пропущена запятая:

Suggested change
В данном примере, если в публичном конструкторе будет выброшено исключение, то утечек данных не произойдет, так как приватный конструктор уже закончит свое исполнение к моменту выброса исключения и будет вызван деструктор, который высвободит выделенные ресурсы.
В данном примере, если в публичном конструкторе будет выброшено исключение, то утечек данных не произойдет, так как приватный конструктор уже закончит свое исполнение к моменту выброса исключения, и будет вызван деструктор, который высвободит выделенные ресурсы.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а где-то написано о том, почему достаточно срабатывания приватного конструктора? если нет, лучше явно написать


## Гарантии исключений.
Уважаемые знатоки, внимание, вопрос. Через одну минуту найдите ошибку в следующем операторе присваивания для строк:
```c++
Expand Down