Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9711c69
fixup
GoPavel Oct 7, 2017
05ad47f
add .gitignore
sorokin Oct 8, 2017
ae21b8c
add Makefile
sorokin Oct 8, 2017
dee24e0
better README
sorokin Oct 8, 2017
477e749
rewrite the chapter about nullptr
sorokin Oct 8, 2017
d79b698
split notes into into different files
sorokin Oct 8, 2017
c329cc8
better wording for nullptr.tex
sorokin Oct 8, 2017
c0b5d68
/
GoPavel Oct 10, 2017
8a566cf
add FAQ
GoPavel Oct 10, 2017
44e672e
typo
GoPavel Oct 10, 2017
8c169d3
add git tutorial
GoPavel Oct 10, 2017
cf212bf
typo
GoPavel Oct 10, 2017
8461cc2
add link's color
GoPavel Oct 11, 2017
5042a44
Merge remote-tracking branch 'sorokin/master'
GoPavel Oct 11, 2017
d3ef4e2
merge with sorokin/master
GoPavel Oct 11, 2017
d819865
rm local file
GoPavel Oct 13, 2017
8081924
from main branch
GoPavel Oct 13, 2017
6c86d63
add notes of exception and RAII
GoPavel Nov 11, 2017
ed19378
ind
GoPavel Nov 11, 2017
f1021bf
some stuff
GoPavel Nov 11, 2017
1c81478
Merge branch 'master' of https://github.com/GoPavel/cpp-notes
GoPavel Nov 11, 2017
6fce838
stuff
GoPavel Nov 11, 2017
d905bf0
add Exceptions(Golovin) and Perfect Forwarding(Kokorin)
GoPavel Dec 21, 2017
636e810
editorial for exceptions
sorokin Dec 27, 2017
4c49cc4
more TODOs
sorokin Dec 27, 2017
55ebe8e
Exception-safety review
sorokin Dec 27, 2017
14ee116
typo
GoPavel Dec 29, 2017
d2ae7b7
Merge remote-tracking branch 'sorokin/exceptions-wip'
GoPavel Dec 29, 2017
516fd46
fixup: rewrite exception-safety and some todo
GoPavel Dec 29, 2017
f038d93
another example
sorokin Dec 30, 2017
ace4cba
a snippet
sorokin Dec 30, 2017
ca9cc15
fixed last TODO in Except and Except-safety
GoPavel Jan 5, 2018
00264d8
Merge remote-tracking branch 'sorokin/exceptions-wip-2'
GoPavel Jan 5, 2018
48ef82c
Merge remote-tracking branch 'sorokin/exceptions-wip-3'
GoPavel Jan 5, 2018
ac11720
Fixed TODO 3 (RAII) and typo
GoPavel Jan 6, 2018
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
126 changes: 126 additions & 0 deletions Exception_and_RAII/Exception-safety.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
\section{Exception safety}
\subsection{Мотивирующий пример}
Рассмотрим оператор копирования для \mintinline{c++}{string}.
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
string& string::operator=(string const &other) {
size = other.size;
delete[] data;
data = new char[size];
memcpy(data, other.data, sizeof(other.data);
return *this;
}
\end{minted}

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

Мы не ловим исключение, поэтому оно пролетит дальше. Предположим, что его кто-то поймал. Тогда он обнаружит, что наша строка в сломанном состоянии и он не может ничего с ней сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушились инварианты класса: data указывает на удаленную память, а size при этом имеет не нулевое значение.

Напишем аккуратнее:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
string& string::operator=(string const &other) {
char *buf = new char[other.size];
memcpy(buf, other,data, sizeof(other.data);
size = other.size;
delete[] data;
data = temp;
return *this;
}
\end{minted}

Если исключения не возникнет, то функция скопирует данные, иначе объект останется неизменным. Это позволяет пользователю класса корректно обрабатывать ошибки, используя public-методы класса, так как инварианты выполняются. Также важно, что теперь при возникновении исключения данные не теряются.

Давайте обобщим этот пример:

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

Это идея развилась в Гарантии безопасности исключений.

\subsection{Определение}

Гарантии безопасности исключений (\textit{англ.} Exception safety) -- это контракт для методов класса относительно исключений.

Уровни гарантий:
\begin{enumerate}
\item \textbf{<<Basic guarantees>>} -- Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов.
\item \textbf{<<Strong guarantees>>} -- Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект.
\item \textbf{<<Nothrow guarantees>>} -- Кроме базовой гарантии, гарантируется, что исключения не возникают.
\item \textbf{<<No guarantees>>} -- нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя.
\end{enumerate}

\textcolor{red}{NB}) Отдельно стоит сказать про гарантии безопасности конструкторов/деструкторов, так как до/после их вызова объекта не существует. Поэтому выделим для них только два вида гарантии: \textbf{Nothrow} и \textbf{Strong}.

\textcolor{red}{NB}) Определения можно аналогично использовать не только для описания состояния объектов некоторого класса, но и для описания состояния программы в целом.

Например, строгая гарантия говорит, что либо функция выполнилась успешно, либо состояние программы не изменилось.



Разберем пару примеров.
\begin{itemize}
\item \mintinline{c++}{std::swap} -- имеет гарантию \textit{Nothrow}. То есть при любых обстоятельствах нам гарантируется, что функция отработает корректно.

Также этой гарантии отвечает \mintinline{c++}{pop_back()}.

\item \mintinline{c++}{operator=} -- нелья сделать \textit{Nothrow}, так необходимо выделить память. Но мы можем сделать Strong гарантию, если скопируем данные в временный объект, а потом сделать \mintinline{c++}{swap()}, если все прошло успешно. Иначе временный объект удалиться и \mintinline{c++}{this} не измениться.
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
template <typename T>
vector<T>& vector<T>::operator=(vector const& other) {
return this->swap(vector(other)); // swap trick!
}
\end{minted}
Такой оператор копирования будет отвечать строгой гарантии, если \mintinline{c++}{std::swap} не будет бросать и конструктор будет отвечать хотя бы строгой гарантии.

\textcolor{red}{NB}) Этот метод называется swap trick. Его суть заключается в том, что мы делаем операции отвечающие хотя бы базовой гарантии во временном объекте. После чего заменяем им наш текущий объект. Если исключение произойдет до замены, то изначальное состояние объекта не потеряется.

\item
Всегда ли можно предоставить строгую гаратию?
Рассмотрим следующий код:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
void g() {
f();
}
\end{minted}
Необходимо предоставить для функции \mintinline{c++}{g()} строгую гарантию. Если функция \mintinline{c++}{f()} отвечает только базовой гарантии, то необходимо запомнить перед ее вызовом состояние программы. Не всегда достаточно запомнить только копию объекта. Если \mintinline{c++}{f()} именяет глобальные данные, то придется запомнить и их состояние.
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
void g() {
// делаем копию данных
try {
f();
} catch (...) {
// восстановить состояние.
throw; // пробросить
}
}
\end{minted}
Тут может быть несколько проблем. Во-первых делать копию состояния может быть дорого. Во-вторых это может быть просто не возможно. Например, если изменилось состояние базы данных, то откатить его нет возможности.

Поэтому иногда предоставить базовую гарантию -- лучшее решение.
\end{itemize}

\subsection{Best practice}
\begin{itemize}

\item
Старайтесь предоставлять самую сильную гарантию, где это оправдано.
Как минимум, методы пользовательского интерфейса должны удовлетворять хотя бы какой-то гарантии. Это избавляет пользователей от утечек памяти и инвалидных данных.

\item
По возможности деструкторы должны отвечать гарантии Nothrow, так как они могут быть вызваны по время раскрутки стека. И если в это время произойдет исключение, то программа будет завершена функцией \mintinline{c++}{std::terminate()}.

Начиная с c++11 все деструкторы неявно помечены как \mintinline{c++}{noexcept}.

\item Используйте swap trick.

\item Спецификатор \mintinline{c++}{noexcept} (C++11) указывает компилятору, что выполняется гарантия nothrow.

Это важная информация для компилятора, которая позволят делать некоторые оптимизации кода связанные с проверкой исключений.

Также это может быть важно при использовании STL. Так как при перемещении бывает сложно обрабатывать исключения, то стандартные алгоритмы могут игнорировать ваш конструктор перемещения если он не помечен как \mintinline{c++}{noexcept}.

\item
Главным способом предотвращения утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже).

\end{itemize}
\textcolor{red}{Offtop:}

Можно попросить оператор \mintinline{c++}{new} не кидать исключение с помощью константы \mintinline{c++}{std::nothrow}
Loading