From 9711c6946b61de442bfe8d7d20c30be8cd8dec12 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Sat, 7 Oct 2017 17:12:46 +0300 Subject: [PATCH 01/30] fixup --- lecture_21.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lecture_21.tex b/lecture_21.tex index ed2a10d..4ceec29 100644 --- a/lecture_21.tex +++ b/lecture_21.tex @@ -32,7 +32,7 @@ \tableofcontents \newpage -\section{Зачем ptr? Ведь в C их нет.} +\section{Зачем nullptr? Ведь в C их нет.} В C нет явных приведений {\bf int $\to$ int* }, не смотря на \textcolor{magenta}{NULL} (это макрос: {\bf \#define \textcolor{magenta}{NULL} 0}). From 05ad47fae6232e1a68ae83801d928e2595740d68 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 8 Oct 2017 15:21:37 +0300 Subject: [PATCH 02/30] add .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d437add --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.aux +*.log +*.out +*.pdf +*.toc +_minted-* From ae21b8cd23e4becf00051a2c844bef0de1d761e7 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 8 Oct 2017 15:35:59 +0300 Subject: [PATCH 03/30] add Makefile --- Makefile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ed0c85c --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +MAIN_FILE_BASE:=lecture_21 +TEX?=pdflatex + +define rm_all + rm -f ${MAIN_FILE_BASE}.aux ${MAIN_FILE_BASE}.log ${MAIN_FILE_BASE}.out + rm -f ${MAIN_FILE_BASE}.pdf ${MAIN_FILE_BASE}.toc + rm -rf _minted-${MAIN_FILE_BASE} +endef + +${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex + $(call rm_all) + ${TEX} -shell-escape ${MAIN_FILE_BASE}.tex + ${TEX} -shell-escape ${MAIN_FILE_BASE}.tex + +.PHONY: clean +clean: + $(call rm_all) From dee24e02482d84557145e49cb01781c428e5657b Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 8 Oct 2017 15:44:52 +0300 Subject: [PATCH 04/30] better README --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9dfa0b1..90299da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ -# How to clone this repository +# How to get started -`git clone git@github.com:sorokin/cpp-notes.git` +To build these notes the following programs need to be installed: +* latex +* minted +* pygments + +To download and build them execute the following commands: +``` +$ git clone git@github.com:sorokin/cpp-notes.git +$ cd cpp-notes +$ make +``` From 477e7495d76e3da943362cfe943f70f2da77ccdb Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 8 Oct 2017 19:45:23 +0300 Subject: [PATCH 05/30] rewrite the chapter about nullptr --- lecture_21.tex | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/lecture_21.tex b/lecture_21.tex index 4ceec29..5abb913 100644 --- a/lecture_21.tex +++ b/lecture_21.tex @@ -32,14 +32,50 @@ \tableofcontents \newpage -\section{Зачем nullptr? Ведь в C их нет.} -В C нет явных приведений {\bf int $\to$ int* }, не смотря на \textcolor{magenta}{NULL} (это макрос: {\bf \#define \textcolor{magenta}{NULL} 0}). +\section{Зачем nullptr? Ведь в C он был не нужен.} +В C++ 0 используется и как число 0 и как нулевой указатель. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int i = 0; +void* p = 0; // OK +\end{minted} +При этом 0 имеет тип {\bf int}. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T arg); +f(0); // calls f(0) +\end{minted} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void g(int); +void g(void*); + +f(0); // calls g(int) +\end{minted} -В C++ ситуация другая: при подстановки \textcolor{magenta}{NULL} в шаблонную функцию нельзя вывести из 0 тип указателя, так как это int. Поэтому существует nullptr. Идея в том, чтобы заменить значение переменной на выражение. Это связано с тем, что к указателю можно привести только выражение, которое в compile-time равно 0, но не int. +Даже макрос \textcolor{magenta}{NULL} определен как {\bf \#define \textcolor{magenta}{NULL} 0}. +Несмотря на это, приведения {\bf int} в указатель нет: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int i = 0; +void* p = i; // error: invalid conversion from 'int' to 'void*' +\end{minted} +Дело в том, что хотя 0 и имеет тип int, есть специальное правило, что rvalue интегрального типа с compile-time значением 0 приводимо в указатель. Получившийся указатель будет нулевым указателем. Следствием этого правила является то, что любое compile-time выражение вычисляемое в 0 можно ипользовать для получения нулевого указателя: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void* p = 2 - 2; // OK в C++03 +void* p = 2 + 2; // error: invalid conversion from 'int' to 'void*' +\end{minted} +В С++11 это правило покрутили и теперь только целочесленный литерал может использоваться в качестве нулевого указателя. +До появления в языке шаблонов, два разных применения 0 не создавали проблем. Но с появлением шаблонов возникла следующая проблема при форвандинге. Предположим мы хотим, чтобы {\bf f} форвардил аргумент в {\bf g}. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T const& arg) +{ + g(arg); +} +\end{minted} +При такой перегрузке, если есть перегрузка {\bf g(int)}, {\bf f} можно вызвать от {\bf int}. Если есть перегрузка {\bf g(std::string)}, {\bf f} можно вызвать от {\bf std::string}. Но если есть перегрузка {\bf g(void*)}, {\bf f(0)} вызвать нельзя. Дело в том, что поскольку 0 имеет тип {\bf int}, будет вызвана {\bf f(0)} и {\bf arg} внутри {\bf f} не является compile-time константой, следовательно он не может быть преоразован в {\bf void*} принимаемый {\bf g}. -\textcolor{red}{NB}) Из этого следует, что можно привести 2 - 2 к int*, то есть наверно можно сделать {\bf \#define \textcolor{magenta}{NULL} 2 - 2; } :) +В C++11 появился специальный литерал называемый {\bf nullptr}. Он имеет тип {\bf std::nullptr\_t} и может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. \section{Предыстория к теме rvalue-reference.} Здесь речь пойдет о некоторых узких моментах языка на период С++03, которые и послужили мотивацией к ввидению в язык rvalue-reference. From d79b698ec43a3f87994a664cabd133824a6467a9 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 8 Oct 2017 19:53:42 +0300 Subject: [PATCH 06/30] split notes into into different files --- Makefile | 7 ++- main.tex | 38 ++++++++++++ nullptr.tex | 44 ++++++++++++++ lecture_21.tex => rvalue-references.tex | 81 ------------------------- 4 files changed, 86 insertions(+), 84 deletions(-) create mode 100644 main.tex create mode 100644 nullptr.tex rename lecture_21.tex => rvalue-references.tex (76%) diff --git a/Makefile b/Makefile index ed0c85c..c0571ed 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,14 @@ -MAIN_FILE_BASE:=lecture_21 +MAIN_FILE_BASE:=main TEX?=pdflatex define rm_all - rm -f ${MAIN_FILE_BASE}.aux ${MAIN_FILE_BASE}.log ${MAIN_FILE_BASE}.out + rm -f *.aux + rm -f ${MAIN_FILE_BASE}.log ${MAIN_FILE_BASE}.out rm -f ${MAIN_FILE_BASE}.pdf ${MAIN_FILE_BASE}.toc rm -rf _minted-${MAIN_FILE_BASE} endef -${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex +${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex nullptr.tex rvalue-references.tex $(call rm_all) ${TEX} -shell-escape ${MAIN_FILE_BASE}.tex ${TEX} -shell-escape ${MAIN_FILE_BASE}.tex diff --git a/main.tex b/main.tex new file mode 100644 index 0000000..0d61195 --- /dev/null +++ b/main.tex @@ -0,0 +1,38 @@ +\documentclass[12pt]{article} + +\usepackage[utf8]{inputenc} % common +\usepackage[russian]{babel} % for russian lang + +\usepackage{hyperref} % for link +\usepackage{graphicx} % for add picture +%\usepackage{daytime} % for displaying version number and date +\usepackage{datetime} % for current data +\usepackage{indentfirst} % Красная строка +\usepackage[usenames]{color} % for \textcolor +\usepackage{minted} % for highlight + +\voffset=-20mm +\textheight=220mm +\hoffset=-25mm +\textwidth=180mm + +\begin{document} + \begin{center} + + {\Large \bf Лекция по С++ \#21} \\ + \vspace{0.5em} + {\Large \bf Тема: smart\_ptr, value, pot} \\ + \vspace{0.5em} + {\large Собрано {\today} в {\currenttime}} + + + \end{center} +\underline{\hbox to 1\textwidth{{ } \hfil{ } \hfil{ } }} + +\tableofcontents + +\newpage +\include{nullptr} +\include{rvalue-references} + +\end{document} diff --git a/nullptr.tex b/nullptr.tex new file mode 100644 index 0000000..572547b --- /dev/null +++ b/nullptr.tex @@ -0,0 +1,44 @@ +\section{Зачем nullptr? Ведь в C он был не нужен.} +В C++ 0 используется и как число 0 и как нулевой указатель. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int i = 0; +void* p = 0; // OK +\end{minted} +При этом 0 имеет тип {\bf int}. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T arg); + +f(0); // calls f(0) +\end{minted} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void g(int); +void g(void*); + +f(0); // calls g(int) +\end{minted} + +Даже макрос \textcolor{magenta}{NULL} определен как {\bf \#define \textcolor{magenta}{NULL} 0}. +Несмотря на это, приведения {\bf int} в указатель нет: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int i = 0; +void* p = i; // error: invalid conversion from 'int' to 'void*' +\end{minted} +Дело в том, что хотя 0 и имеет тип int, есть специальное правило, что rvalue интегрального типа с compile-time значением 0 приводимо в указатель. Получившийся указатель будет нулевым указателем. Следствием этого правила является то, что любое compile-time выражение вычисляемое в 0 можно ипользовать для получения нулевого указателя: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void* p = 2 - 2; // OK в C++03 +void* p = 2 + 2; // error: invalid conversion from 'int' to 'void*' +\end{minted} +В С++11 это правило покрутили и теперь только целочесленный литерал может использоваться в качестве нулевого указателя. + +До появления в языке шаблонов, два разных применения 0 не создавали проблем. Но с появлением шаблонов возникла следующая проблема при форвандинге. Предположим мы хотим, чтобы {\bf f} форвардил аргумент в {\bf g}. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T const& arg) +{ + g(arg); +} +\end{minted} +При такой перегрузке, если есть перегрузка {\bf g(int)}, {\bf f} можно вызвать от {\bf int}. Если есть перегрузка {\bf g(std::string)}, {\bf f} можно вызвать от {\bf std::string}. Но если есть перегрузка {\bf g(void*)}, {\bf f(0)} вызвать нельзя. Дело в том, что поскольку 0 имеет тип {\bf int}, будет вызвана {\bf f(0)} и {\bf arg} внутри {\bf f} не является compile-time константой, следовательно он не может быть преоразован в {\bf void*} принимаемый {\bf g}. + +В C++11 появился специальный литерал называемый {\bf nullptr}. Он имеет тип {\bf std::nullptr\_t} и может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. diff --git a/lecture_21.tex b/rvalue-references.tex similarity index 76% rename from lecture_21.tex rename to rvalue-references.tex index 5abb913..052ced3 100644 --- a/lecture_21.tex +++ b/rvalue-references.tex @@ -1,82 +1,3 @@ -\documentclass[12pt]{article} - -\usepackage[utf8]{inputenc} % common -\usepackage[russian]{babel} % for russian lang - -\usepackage{hyperref} % for link -\usepackage{graphicx} % for add picture -%\usepackage{daytime} % for displaying version number and date -\usepackage{datetime} % for current data -\usepackage{indentfirst} % Красная строка -\usepackage[usenames]{color} % for \textcolor -\usepackage{minted} % for highlight - -\voffset=-20mm -\textheight=220mm -\hoffset=-25mm -\textwidth=180mm - -\begin{document} - \begin{center} - - {\Large \bf Лекция по С++ \#21} \\ - \vspace{0.5em} - {\Large \bf Тема: smart\_ptr, value, pot} \\ - \vspace{0.5em} - {\large Собрано {\today} в {\currenttime}} - - - \end{center} -\underline{\hbox to 1\textwidth{{ } \hfil{ } \hfil{ } }} - -\tableofcontents - -\newpage -\section{Зачем nullptr? Ведь в C он был не нужен.} -В C++ 0 используется и как число 0 и как нулевой указатель. -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -int i = 0; -void* p = 0; // OK -\end{minted} -При этом 0 имеет тип {\bf int}. -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void f(T arg); - -f(0); // calls f(0) -\end{minted} -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -void g(int); -void g(void*); - -f(0); // calls g(int) -\end{minted} - -Даже макрос \textcolor{magenta}{NULL} определен как {\bf \#define \textcolor{magenta}{NULL} 0}. -Несмотря на это, приведения {\bf int} в указатель нет: -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -int i = 0; -void* p = i; // error: invalid conversion from 'int' to 'void*' -\end{minted} -Дело в том, что хотя 0 и имеет тип int, есть специальное правило, что rvalue интегрального типа с compile-time значением 0 приводимо в указатель. Получившийся указатель будет нулевым указателем. Следствием этого правила является то, что любое compile-time выражение вычисляемое в 0 можно ипользовать для получения нулевого указателя: -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -void* p = 2 - 2; // OK в C++03 -void* p = 2 + 2; // error: invalid conversion from 'int' to 'void*' -\end{minted} -В С++11 это правило покрутили и теперь только целочесленный литерал может использоваться в качестве нулевого указателя. - -До появления в языке шаблонов, два разных применения 0 не создавали проблем. Но с появлением шаблонов возникла следующая проблема при форвандинге. Предположим мы хотим, чтобы {\bf f} форвардил аргумент в {\bf g}. -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void f(T const& arg) -{ - g(arg); -} -\end{minted} -При такой перегрузке, если есть перегрузка {\bf g(int)}, {\bf f} можно вызвать от {\bf int}. Если есть перегрузка {\bf g(std::string)}, {\bf f} можно вызвать от {\bf std::string}. Но если есть перегрузка {\bf g(void*)}, {\bf f(0)} вызвать нельзя. Дело в том, что поскольку 0 имеет тип {\bf int}, будет вызвана {\bf f(0)} и {\bf arg} внутри {\bf f} не является compile-time константой, следовательно он не может быть преоразован в {\bf void*} принимаемый {\bf g}. - -В C++11 появился специальный литерал называемый {\bf nullptr}. Он имеет тип {\bf std::nullptr\_t} и может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. - \section{Предыстория к теме rvalue-reference.} Здесь речь пойдет о некоторых узких моментах языка на период С++03, которые и послужили мотивацией к ввидению в язык rvalue-reference. \subsection{Что можно положить в вектор?} @@ -356,5 +277,3 @@ \subsection{Когда необходимо копирование?} \textcolor{red}{NB}) Может быть проблема с исключениями: ??? %TODO - -\end{document} From c329cc8e53d93590d1312e587239712219aa80d2 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Mon, 9 Oct 2017 00:09:25 +0300 Subject: [PATCH 07/30] better wording for nullptr.tex --- nullptr.tex | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/nullptr.tex b/nullptr.tex index 572547b..10cf5c6 100644 --- a/nullptr.tex +++ b/nullptr.tex @@ -1,37 +1,32 @@ -\section{Зачем nullptr? Ведь в C он был не нужен.} -В C++ 0 используется и как число 0 и как нулевой указатель. +\section{Нулевой указатель.} +В C++ литерал 0 используется и как число 0 и как нулевой указатель. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int i = 0; void* p = 0; // OK \end{minted} -При этом 0 имеет тип {\bf int}. +Несмотря на двойное назначение, 0 имеет тип {\bf int}, проверить это можно следующим кодом: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template void f(T arg); - -f(0); // calls f(0) -\end{minted} -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void g(int); void g(void*); -f(0); // calls g(int) +f(0); // calls f(0) +g(0); // calls g(int) \end{minted} - -Даже макрос \textcolor{magenta}{NULL} определен как {\bf \#define \textcolor{magenta}{NULL} 0}. -Несмотря на это, приведения {\bf int} в указатель нет: +Хотя 0 имеет тип {\bf int} и приводиться в указатель, в общем случае приведение {\bf int} в указатель запрещено: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int i = 0; void* p = i; // error: invalid conversion from 'int' to 'void*' \end{minted} -Дело в том, что хотя 0 и имеет тип int, есть специальное правило, что rvalue интегрального типа с compile-time значением 0 приводимо в указатель. Получившийся указатель будет нулевым указателем. Следствием этого правила является то, что любое compile-time выражение вычисляемое в 0 можно ипользовать для получения нулевого указателя: +Если {\bf int} не приводится в указатель, то почему 0 приводится? Дело в том, что в C и в C++03 существует специальное правило, что rvalue интегрального типа с compile-time значением 0 приводимо в указатель. Получившийся указатель будет нулевым указателем. Следствием этого правила является то, что любое compile-time выражение вычисляемое в 0 можно использовать для получения нулевого указателя: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void* p = 2 - 2; // OK в C++03 void* p = 2 + 2; // error: invalid conversion from 'int' to 'void*' \end{minted} -В С++11 это правило покрутили и теперь только целочесленный литерал может использоваться в качестве нулевого указателя. +В С++11 это правило покрутили и теперь только целочисленный литерал может использоваться в качестве нулевого указателя. -До появления в языке шаблонов, два разных применения 0 не создавали проблем. Но с появлением шаблонов возникла следующая проблема при форвандинге. Предположим мы хотим, чтобы {\bf f} форвардил аргумент в {\bf g}. +До появления в языке шаблонов, два разных применения 0 не создавали проблем. Но с появлением шаблонов возникла следующая проблема при форвандинге. Предположим мы хотим, чтобы {\bf f} форвардил свой аргумент в {\bf g}. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template void f(T const& arg) @@ -41,4 +36,7 @@ \section{Зачем nullptr? Ведь в C он был не нужен.} \end{minted} При такой перегрузке, если есть перегрузка {\bf g(int)}, {\bf f} можно вызвать от {\bf int}. Если есть перегрузка {\bf g(std::string)}, {\bf f} можно вызвать от {\bf std::string}. Но если есть перегрузка {\bf g(void*)}, {\bf f(0)} вызвать нельзя. Дело в том, что поскольку 0 имеет тип {\bf int}, будет вызвана {\bf f(0)} и {\bf arg} внутри {\bf f} не является compile-time константой, следовательно он не может быть преоразован в {\bf void*} принимаемый {\bf g}. -В C++11 появился специальный литерал называемый {\bf nullptr}. Он имеет тип {\bf std::nullptr\_t} и может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. +Для разрешения этой проблемы в C++11 появился специальный литерал называемый {\bf nullptr}. Он имеет тип {\bf std::nullptr\_t}, который может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. + +В C++ существует заимствованный из C макрос \textcolor{magenta}{NULL}, который определен как {\bf \#define \textcolor{magenta}{NULL} 0}. При переходе на C++11 было бы возможно изменить его определение так, чтобы он раскрывался в {\bf nullptr}, а не в 0. Это не было сделано из соображений совместимости и целесообразности(нафига?). +Поскольку \textcolor{magenta}{NULL} просто раскрывается в 0, его использование имеет те же недостатки, что и использование литерала 0. В C++11 следует предпочитать использовать {\bf nullptr} вместо \textcolor{magenta}{NULL} или 0. From c0b5d6826f83f9b6c05c594750b0f50dd3e3c237 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Tue, 10 Oct 2017 23:09:09 +0300 Subject: [PATCH 08/30] / --- main.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.tex b/main.tex index 0d61195..da48b2c 100644 --- a/main.tex +++ b/main.tex @@ -19,9 +19,9 @@ \begin{document} \begin{center} - {\Large \bf Лекция по С++ \#21} \\ + {\Large \bf Конспекты по C++} \\ \vspace{0.5em} - {\Large \bf Тема: smart\_ptr, value, pot} \\ + {\Large \bf Руководитель: Иван Сорокин} \\ \vspace{0.5em} {\large Собрано {\today} в {\currenttime}} From 8a566cf49434a9abc1feffef323bc404694bf01d Mon Sep 17 00:00:00 2001 From: GoPavel Date: Tue, 10 Oct 2017 23:11:00 +0300 Subject: [PATCH 09/30] add FAQ --- FAQ/FAQ.tex | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 FAQ/FAQ.tex diff --git a/FAQ/FAQ.tex b/FAQ/FAQ.tex new file mode 100644 index 0000000..1abc71e --- /dev/null +++ b/FAQ/FAQ.tex @@ -0,0 +1,116 @@ +\documentclass[12pt]{article} + +\usepackage[utf8]{inputenc} % common +\usepackage[russian]{babel} % for russian lang + +\usepackage{hyperref} % for link +\usepackage{graphicx} % for add picture +%\usepackage{daytime} % for displaying version number and date +\usepackage{datetime} % for current data +\usepackage{indentfirst} % Красная строка +\usepackage[usenames]{color} % for \textcolor +\usepackage{minted} % for highlight + +% seting size page +\voffset=-20mm +\textheight=220mm +\hoffset=-25mm +\textwidth=180mm + +\begin{document} + \begin{center} + + {\Large \bf F.A.Q. для конспектов} \\ + \vspace{0.5em} + {\large Собрано {\today} в {\currenttime}} + + Эта статья призвана помочь писать конспекты и стандартизировать их написания. Я не буду рассказвать все о LaTeX я только опишу инструменты, которыми стоит советую пользоваться, и которых я надеюсь вам будет достаточно. + + + P. S. сложно добиться того, чтобы только из pdf получилась понятная статья, поэтому это статья-пример, которой слудует пользоваться так: смотрим код, потом смотрим во что это компилируется. + + \end{center} +\underline{\hbox to 1\textwidth{{ } \hfil{ } \hfil{ } }} + +\tableofcontents + +\newpage + +\section{Установка LaTeX} + +\subsection{Linux} + +Вам нужно установить: +\begin{enumerate} + \item texlive-live - сам LaTeX + \item minted - для вставки исходного кода + \item pygment - для minted + +\textcolor{red}{NB}) Для сборки tex-файла нужно указывать в командной строке флаг \textbf(--shell-escape) +\end{enumerate} + +\subsection{Win} + TODO +\section{Работа с Git} + \subsection{Начало работы с основным репрезиторем} + TODO + \subsection{Обновление локальной копии основного репрезитория} + TODO + \subsection{Залив в основной репрезиторий} + TODO +\section{LaTeX} + TODO + +\subsection{Структура} + +\begin{enumerate} + \item Когда вы пишите статью, лучше всего делить ее на секции и подсекции, которые потом будут отражаться в оглавлении. Чем лучше структурирован текст, тем проще ориентироваться в оглавлении. +\end{enumerate} + +\subsection{Вставка кода} + +Для вставки исходного кода можно искользовать разные средства. Я остановился на \textbf{minted}. Мне показалось, что это очень удобная и красивая библиотека. Перейдем к примерам: + + +Вставим код: +\begin{minted}{c++} + + #include + + using namespace std; + + int main() { + cout << "Hello, world!" << endl; + + return 0; + } +\end{minted} + +Хорошо теперь наведем порядок, написав преамбулу. + +\begin{minted} + [linenos, % нумерация строк + frame=lines, framesep=2mm, % черта сверху и снизу + tabsize = 4, % размер табуляции + breaklines % перенос текста + ]{c++} + + #include + + using namespace std; + + int main() { + cout << "Hello, world!" << endl; // comment + return 0; + } +\end{minted} + +Также можно вставлять код прямо в с текст \mintinline{c++}{std::shared_ptr; // smart pointer }. И я рекомендую делать именно так. + + +\textcolor{red}{NB}) Есть различные стили кода, но стандартная вроде не плоха. +\section{Советы и пожелания} + TODO + + +\end{document} From 44e672e0fcdeab8b2094bf0c79aa049bbe41adb0 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Tue, 10 Oct 2017 23:12:29 +0300 Subject: [PATCH 10/30] typo --- rvalue-references.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rvalue-references.tex b/rvalue-references.tex index 052ced3..f5cef15 100644 --- a/rvalue-references.tex +++ b/rvalue-references.tex @@ -72,7 +72,7 @@ \subsubsection{Оптимизация по памяти.} D *deleter; // функция, которая удаляет объект }* con_bl; shared_ptr(*T, D const& deleter); -} +}; \end{minted} From 8c169d31cb58da542583dc963f2a0a2e68d1c630 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Wed, 11 Oct 2017 00:28:04 +0300 Subject: [PATCH 11/30] add git tutorial --- FAQ/FAQ.tex | 54 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/FAQ/FAQ.tex b/FAQ/FAQ.tex index 1abc71e..1bdda52 100644 --- a/FAQ/FAQ.tex +++ b/FAQ/FAQ.tex @@ -18,6 +18,7 @@ \textwidth=180mm \begin{document} + \begin{center} {\Large \bf F.A.Q. для конспектов} \\ @@ -45,19 +46,57 @@ \subsection{Linux} \item texlive-live - сам LaTeX \item minted - для вставки исходного кода \item pygment - для minted +\end{enumerate} + \textcolor{red}{NB}) Для сборки tex-файла нужно указывать в командной строке флаг \textbf(--shell-escape) -\end{enumerate} + + +\textcolor{red}{NB}) \href{http://tug.ctan.org/macros/latex/contrib/minted/minted.pdf}{Документация для minted} \subsection{Win} TODO \section{Работа с Git} +Для того, чтобы работать над конспектом необходимо использовать git. Поэтому я привел несколько ситуации, в которые вы неизбежно попадете. + + +{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной перезиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной реперезиторий ({\bf pull request}). \subsection{Начало работы с основным репрезиторем} - TODO - \subsection{Обновление локальной копии основного репрезитория} - TODO + \begin{enumerate} + \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репезитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) + + + Теперь ответвление репезитория будет у вас на гите. Вы просто работает с ним как со своим. + \item делаем clone на свой компьютер: + \begin{minted}{bash} + git clone git://github.com/my_name/cpp-notes.git + \end{minted} + \end{enumerate} + \subsection{Работа с ответвлением} + Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репезиторий. + + + \textcolor{red}{NB}) Для тех, кому не все просто, отправляю учить \href{https://git-scm.com/book/ru/v1}{основы git} \subsection{Залив в основной репрезиторий} - TODO + Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения предложатся создателю репезетория. + + \subsection{Добавление изменений основного репезитория в ваш} + Рассмотрим такую ситацию: вы работаете над статьей. И в это время, кто-то меняет основной репезиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? + Решение: + \begin{minted}[breaklines]{bash} + git remote add sorokin "https://github.com/sorokin/cpp-notes.git" # добавим основной репрезиторий в remote + git remote -v # убедимся, что он добавился в remote + git fetch sorokin # сделаем из него ветку + git branch -v # убедимся, что появилась новая ветка со всем нужными нами изменениями + git merge sorokin/master #сливаемся с новой веткой + git push # заливаем все на нас сервер + \end{minted} + + +\textcolor{red}{NB}) После того, как мы получили ветку основного репезитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. + + +\textcolor{red}{NB}) Если кто не силен в ветвлении, смотрите все туже \href{https://git-scm.com/book/ru/v1}{книгу}. \section{LaTeX} TODO @@ -109,6 +148,11 @@ \subsection{Вставка кода} \textcolor{red}{NB}) Есть различные стили кода, но стандартная вроде не плоха. + +\subsection{Вставка ссылок и гипер ссылок} + +\href{http://blog.harrix.org/article/661#h2_2}{Вот статья на эту тему)} + \section{Советы и пожелания} TODO From cf212bf8e755b0f75cc26c0019149921ac548bf2 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Wed, 11 Oct 2017 01:21:53 +0300 Subject: [PATCH 12/30] typo --- FAQ/FAQ.tex | 40 ++++++++++++++++++++-------------------- Makefile | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/FAQ/FAQ.tex b/FAQ/FAQ.tex index 1bdda52..9f9f3e5 100644 --- a/FAQ/FAQ.tex +++ b/FAQ/FAQ.tex @@ -25,10 +25,10 @@ \vspace{0.5em} {\large Собрано {\today} в {\currenttime}} - Эта статья призвана помочь писать конспекты и стандартизировать их написания. Я не буду рассказвать все о LaTeX я только опишу инструменты, которыми стоит советую пользоваться, и которых я надеюсь вам будет достаточно. + Эта статья призвана помочь писать конспекты и стандартизировать их написание. Я не буду рассказывать все о LaTeX. Я только опишу инструменты, которыми я советую пользоваться, и которых я надеюсь вам будет достаточно. - P. S. сложно добиться того, чтобы только из pdf получилась понятная статья, поэтому это статья-пример, которой слудует пользоваться так: смотрим код, потом смотрим во что это компилируется. + P. S. сложно добиться того, чтобы только из pdf получилась понятная статья, поэтому это статья-пример, которой следует пользоваться так: смотрим код, потом смотрим во что это компилируется. \end{center} \underline{\hbox to 1\textwidth{{ } \hfil{ } \hfil{ } }} @@ -60,28 +60,28 @@ \section{Работа с Git} Для того, чтобы работать над конспектом необходимо использовать git. Поэтому я привел несколько ситуации, в которые вы неизбежно попадете. -{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной перезиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной реперезиторий ({\bf pull request}). +{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной репрезиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной репрезиторий ({\bf pull request}). \subsection{Начало работы с основным репрезиторем} \begin{enumerate} - \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репезитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) - - - Теперь ответвление репезитория будет у вас на гите. Вы просто работает с ним как со своим. - \item делаем clone на свой компьютер: + \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репрезитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) + + + Теперь ответвление репрезитория будет у вас на гите. Вы просто работает с ним как со своим. + \item делаем clone на свой компьютер: \begin{minted}{bash} git clone git://github.com/my_name/cpp-notes.git - \end{minted} + \end{minted} \end{enumerate} \subsection{Работа с ответвлением} - Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репезиторий. + Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репрезиторий. + - \textcolor{red}{NB}) Для тех, кому не все просто, отправляю учить \href{https://git-scm.com/book/ru/v1}{основы git} \subsection{Залив в основной репрезиторий} - Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения предложатся создателю репезетория. - - \subsection{Добавление изменений основного репезитория в ваш} - Рассмотрим такую ситацию: вы работаете над статьей. И в это время, кто-то меняет основной репезиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? + Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения предложатся создателю репрезетория. + + \subsection{Добавление изменений основного репрезитория в ваш} + Рассмотрим такую ситуацию: вы работаете над статьей. И в это время, кто-то меняет основной репрезиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? Решение: \begin{minted}[breaklines]{bash} git remote add sorokin "https://github.com/sorokin/cpp-notes.git" # добавим основной репрезиторий в remote @@ -91,9 +91,9 @@ \section{Работа с Git} git merge sorokin/master #сливаемся с новой веткой git push # заливаем все на нас сервер \end{minted} - - -\textcolor{red}{NB}) После того, как мы получили ветку основного репезитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. + + +\textcolor{red}{NB}) После того, как мы получили ветку основного репрезитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. \textcolor{red}{NB}) Если кто не силен в ветвлении, смотрите все туже \href{https://git-scm.com/book/ru/v1}{книгу}. @@ -108,7 +108,7 @@ \subsection{Структура} \subsection{Вставка кода} -Для вставки исходного кода можно искользовать разные средства. Я остановился на \textbf{minted}. Мне показалось, что это очень удобная и красивая библиотека. Перейдем к примерам: +Для вставки исходного кода можно использовать разные средства. Я остановился на \textbf{minted}. Мне показалось, что это очень удобная и красивая библиотека. Перейдем к примерам: Вставим код: @@ -149,7 +149,7 @@ \subsection{Вставка кода} \textcolor{red}{NB}) Есть различные стили кода, но стандартная вроде не плоха. -\subsection{Вставка ссылок и гипер ссылок} +\subsection{Вставка ссылок и гиперссылок} \href{http://blog.harrix.org/article/661#h2_2}{Вот статья на эту тему)} diff --git a/Makefile b/Makefile index c0571ed..951c8c6 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ endef ${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex nullptr.tex rvalue-references.tex $(call rm_all) - ${TEX} -shell-escape ${MAIN_FILE_BASE}.tex - ${TEX} -shell-escape ${MAIN_FILE_BASE}.tex - + ${TEX} --shell-escape ${MAIN_FILE_BASE}.tex + ${TEX} --shell-escape ${MAIN_FILE_BASE}.tex +#pdflatex -synctex=1 -interaction=nonstopmode --shell-escape main.tex .PHONY: clean clean: $(call rm_all) From 8461cc2431121a52d467809674fd7be8f4e855d5 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Wed, 11 Oct 2017 03:01:49 +0300 Subject: [PATCH 13/30] add link's color --- FAQ/FAQ.tex | 19 ++++++++++++++++--- main.tex | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/FAQ/FAQ.tex b/FAQ/FAQ.tex index 9f9f3e5..dea23a2 100644 --- a/FAQ/FAQ.tex +++ b/FAQ/FAQ.tex @@ -3,12 +3,16 @@ \usepackage[utf8]{inputenc} % common \usepackage[russian]{babel} % for russian lang -\usepackage{hyperref} % for link \usepackage{graphicx} % for add picture %\usepackage{daytime} % for displaying version number and date \usepackage{datetime} % for current data -\usepackage{indentfirst} % Красная строка +\usepackage{indentfirst} % Красkная строка \usepackage[usenames]{color} % for \textcolor +\usepackage{xcolor} +\usepackage{hyperref} % for link +\definecolor{linkcolor}{HTML}{799B03} % цвет ссылок +\definecolor{urlcolor}{HTML}{799B03} % цвет гиперссылок +\hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} \usepackage{minted} % for highlight % seting size page @@ -151,7 +155,16 @@ \subsection{Вставка кода} \subsection{Вставка ссылок и гиперссылок} -\href{http://blog.harrix.org/article/661#h2_2}{Вот статья на эту тему)} + \href{http://blog.harrix.org/article/661#h2_2}{Вот статья на эту тему)} + + + \url{http://www-sbras.nsc.ru/win/docs/TeX/LaTex2e/hyperref_options.pdf} + + Вот так можно вставлять ссылки. + + +Также желательно их подсветить в нормальные цвета, что я и сделал в этом документе. Но эти свойства будут прописываться в main.tex, поэтому можно об этом не волноваться. + \section{Советы и пожелания} TODO diff --git a/main.tex b/main.tex index da48b2c..6de5537 100644 --- a/main.tex +++ b/main.tex @@ -9,8 +9,16 @@ \usepackage{datetime} % for current data \usepackage{indentfirst} % Красная строка \usepackage[usenames]{color} % for \textcolor + +\usepackage{xcolor} +\usepackage{hyperref} % for link +\definecolor{linkcolor}{HTML}{799B03} % цвет ссылок +\definecolor{urlcolor}{HTML}{799B03} % цвет гиперссылок +\hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} + \usepackage{minted} % for highlight + \voffset=-20mm \textheight=220mm \hoffset=-25mm From d3ef4e2236adc7128a40011305fa02bdbfc313c0 Mon Sep 17 00:00:00 2001 From: GoPavel Date: Wed, 11 Oct 2017 23:35:41 +0300 Subject: [PATCH 14/30] merge with sorokin/master --- Makefile | 38 ++++++++++++++++++++++++++++---------- README.md | 12 +++++++++--- main.tex | 4 +++- nullptr.tex | 18 +++++++++++++----- rvalue-references.tex | 2 +- 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 951c8c6..31526cd 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,36 @@ -MAIN_FILE_BASE:=main -TEX?=pdflatex +MAIN_FILE_BASE := main +MAIN_FILE_DEPS := compilation.tex preprocessor.tex nullptr.tex rvalue-references.tex +HOW_TO_BASE := how-to-contribute +TEX_CMD ?= pdflatex -define rm_all - rm -f *.aux +define clean_main_file + rm -f ${MAIN_FILE_BASE}.aux $(MAIN_FILE_DEPS:%.tex=%.aux) rm -f ${MAIN_FILE_BASE}.log ${MAIN_FILE_BASE}.out rm -f ${MAIN_FILE_BASE}.pdf ${MAIN_FILE_BASE}.toc rm -rf _minted-${MAIN_FILE_BASE} endef -${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex nullptr.tex rvalue-references.tex - $(call rm_all) - ${TEX} --shell-escape ${MAIN_FILE_BASE}.tex - ${TEX} --shell-escape ${MAIN_FILE_BASE}.tex -#pdflatex -synctex=1 -interaction=nonstopmode --shell-escape main.tex +define clean_how_to_file + rm -f ${HOW_TO_BASE}.aux + rm -f ${HOW_TO_BASE}.log ${HOW_TO_BASE}.out + rm -f ${HOW_TO_BASE}.pdf ${HOW_TO_BASE}.toc + rm -rf _minted-${HOW_TO_BASE} +endef + +.PHONY: all +all: ${MAIN_FILE_BASE}.pdf ${HOW_TO_BASE}.pdf + +${MAIN_FILE_BASE}.pdf: ${MAIN_FILE_BASE}.tex ${MAIN_FILE_DEPS} + $(call clean_main_file) + ${TEX_CMD} --interaction=nonstopmode --halt-on-error --shell-escape ${MAIN_FILE_BASE}.tex + ${TEX_CMD} --interaction=nonstopmode --halt-on-error --shell-escape ${MAIN_FILE_BASE}.tex + +${HOW_TO_BASE}.pdf: ${HOW_TO_BASE}.tex + $(call clean_how_to_file) + ${TEX_CMD} --interaction=nonstopmode --halt-on-error --shell-escape ${HOW_TO_BASE}.tex + ${TEX_CMD} --interaction=nonstopmode --halt-on-error --shell-escape ${HOW_TO_BASE}.tex + .PHONY: clean clean: - $(call rm_all) + $(call clean_main_file) + $(call clean_how_to_file) diff --git a/README.md b/README.md index 90299da..2b3643c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # How to get started -To build these notes the following programs need to be installed: +To build the notes the following programs need to be installed: * latex * minted * pygments -To download and build them execute the following commands: +To download and build the notes execute the following commands: ``` -$ git clone git@github.com:sorokin/cpp-notes.git +$ git clone https://github.com/sorokin/cpp-notes.git $ cd cpp-notes $ make ``` + +After executing these commands two files will be build: +* `main.pdf` — the notes itself +* `how-to-contribute.pdf` — a detailed contributor guide + +For more details, please refer to `how-to-contribute.pdf`. diff --git a/main.tex b/main.tex index 6de5537..8e24d4e 100644 --- a/main.tex +++ b/main.tex @@ -14,7 +14,7 @@ \usepackage{hyperref} % for link \definecolor{linkcolor}{HTML}{799B03} % цвет ссылок \definecolor{urlcolor}{HTML}{799B03} % цвет гиперссылок -\hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} +\hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} \usepackage{minted} % for highlight @@ -40,6 +40,8 @@ \tableofcontents \newpage +\include{compilation} +\include{preprocessor} \include{nullptr} \include{rvalue-references} diff --git a/nullptr.tex b/nullptr.tex index 10cf5c6..34d87db 100644 --- a/nullptr.tex +++ b/nullptr.tex @@ -1,5 +1,5 @@ -\section{Нулевой указатель.} -В C++ литерал 0 используется и как число 0 и как нулевой указатель. +\section{Нулевой указатель} +В C++ литерал 0 используется и как число 0 и как нулевой указатель. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int i = 0; void* p = 0; // OK @@ -14,7 +14,7 @@ \section{Нулевой указатель.} f(0); // calls f(0) g(0); // calls g(int) \end{minted} -Хотя 0 имеет тип {\bf int} и приводиться в указатель, в общем случае приведение {\bf int} в указатель запрещено: +Хотя 0 имеет тип {\bf int} и приводится в указатель, в общем случае приведение {\bf int} в указатель запрещено: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int i = 0; void* p = i; // error: invalid conversion from 'int' to 'void*' @@ -36,7 +36,15 @@ \section{Нулевой указатель.} \end{minted} При такой перегрузке, если есть перегрузка {\bf g(int)}, {\bf f} можно вызвать от {\bf int}. Если есть перегрузка {\bf g(std::string)}, {\bf f} можно вызвать от {\bf std::string}. Но если есть перегрузка {\bf g(void*)}, {\bf f(0)} вызвать нельзя. Дело в том, что поскольку 0 имеет тип {\bf int}, будет вызвана {\bf f(0)} и {\bf arg} внутри {\bf f} не является compile-time константой, следовательно он не может быть преоразован в {\bf void*} принимаемый {\bf g}. -Для разрешения этой проблемы в C++11 появился специальный литерал называемый {\bf nullptr}. Он имеет тип {\bf std::nullptr\_t}, который может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. +Для разрешения этой проблемы в C++11 появился специальный литерал называемый {\bf nullptr}\footnote{\url{http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf} --- предложение по включению {\bf nullptr} в C++.}. Он имеет тип {\bf std::nullptr\_t}, который может быть неявно преобразован в любой указатель. При вызове {\bf f(nullptr)}, {\bf T} будет выведен в {\bf std::nullptr\_t} и внутри {\bf g} {\bf arg} будет преобразован в {\bf void*}. -В C++ существует заимствованный из C макрос \textcolor{magenta}{NULL}, который определен как {\bf \#define \textcolor{magenta}{NULL} 0}. При переходе на C++11 было бы возможно изменить его определение так, чтобы он раскрывался в {\bf nullptr}, а не в 0. Это не было сделано из соображений совместимости и целесообразности(нафига?). +Исторически в C существует макрос \textcolor{magenta}{NULL}, который определен следующим образом: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +#define NULL (void*)0 +\end{minted} +Этот макрос используется в качестве нулевого указателя. В C++ этот макрос заимствован, но поскольку в C++ неявная конверсия из {\bf void*} в указатель другого типа запрещена, в C++ этот макрос в большинстве реализаций определен по-другому: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +#define NULL 0 +\end{minted} +Стандарт C++11 допускает определение \textcolor{magenta}{NULL} через {\bf nullptr}. Однако на данный момент не существует ни одной реализации, которая делала бы так, так как это ломает большое количество кода.\footnote{На \url{https://channel9.msdn.com/Events/GoingNative/GoingNative-2012/STL11-Magic-Secrets} в 55:35 Stephan T. Lavavej рассказывает, как они попытались определить \textcolor{magenta}{NULL} через {\bf nullptr} и почему из этого ничего не вышло} Поскольку \textcolor{magenta}{NULL} просто раскрывается в 0, его использование имеет те же недостатки, что и использование литерала 0. В C++11 следует предпочитать использовать {\bf nullptr} вместо \textcolor{magenta}{NULL} или 0. diff --git a/rvalue-references.tex b/rvalue-references.tex index f5cef15..92ad1a8 100644 --- a/rvalue-references.tex +++ b/rvalue-references.tex @@ -1,4 +1,4 @@ -\section{Предыстория к теме rvalue-reference.} +\section{Предыстория к теме rvalue-reference} Здесь речь пойдет о некоторых узких моментах языка на период С++03, которые и послужили мотивацией к ввидению в язык rvalue-reference. \subsection{Что можно положить в вектор?} Какие требования к типу {\bf T}, чтобы можно было объявить \mintinline{c++}{vector} и потом этим пользоваться: From d819865007cac4f431e4c714cd869ff1f9ad3c70 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Fri, 13 Oct 2017 10:58:16 +0300 Subject: [PATCH 15/30] rm local file --- FAQ/FAQ.tex | 173 ---------------------------------------------------- 1 file changed, 173 deletions(-) delete mode 100644 FAQ/FAQ.tex diff --git a/FAQ/FAQ.tex b/FAQ/FAQ.tex deleted file mode 100644 index dea23a2..0000000 --- a/FAQ/FAQ.tex +++ /dev/null @@ -1,173 +0,0 @@ -\documentclass[12pt]{article} - -\usepackage[utf8]{inputenc} % common -\usepackage[russian]{babel} % for russian lang - -\usepackage{graphicx} % for add picture -%\usepackage{daytime} % for displaying version number and date -\usepackage{datetime} % for current data -\usepackage{indentfirst} % Красkная строка -\usepackage[usenames]{color} % for \textcolor -\usepackage{xcolor} -\usepackage{hyperref} % for link -\definecolor{linkcolor}{HTML}{799B03} % цвет ссылок -\definecolor{urlcolor}{HTML}{799B03} % цвет гиперссылок -\hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} -\usepackage{minted} % for highlight - -% seting size page -\voffset=-20mm -\textheight=220mm -\hoffset=-25mm -\textwidth=180mm - -\begin{document} - - \begin{center} - - {\Large \bf F.A.Q. для конспектов} \\ - \vspace{0.5em} - {\large Собрано {\today} в {\currenttime}} - - Эта статья призвана помочь писать конспекты и стандартизировать их написание. Я не буду рассказывать все о LaTeX. Я только опишу инструменты, которыми я советую пользоваться, и которых я надеюсь вам будет достаточно. - - - P. S. сложно добиться того, чтобы только из pdf получилась понятная статья, поэтому это статья-пример, которой следует пользоваться так: смотрим код, потом смотрим во что это компилируется. - - \end{center} -\underline{\hbox to 1\textwidth{{ } \hfil{ } \hfil{ } }} - -\tableofcontents - -\newpage - -\section{Установка LaTeX} - -\subsection{Linux} - -Вам нужно установить: -\begin{enumerate} - \item texlive-live - сам LaTeX - \item minted - для вставки исходного кода - \item pygment - для minted -\end{enumerate} - - -\textcolor{red}{NB}) Для сборки tex-файла нужно указывать в командной строке флаг \textbf(--shell-escape) - - -\textcolor{red}{NB}) \href{http://tug.ctan.org/macros/latex/contrib/minted/minted.pdf}{Документация для minted} - -\subsection{Win} - TODO -\section{Работа с Git} -Для того, чтобы работать над конспектом необходимо использовать git. Поэтому я привел несколько ситуации, в которые вы неизбежно попадете. - - -{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной репрезиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной репрезиторий ({\bf pull request}). - \subsection{Начало работы с основным репрезиторем} - \begin{enumerate} - \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репрезитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) - - - Теперь ответвление репрезитория будет у вас на гите. Вы просто работает с ним как со своим. - \item делаем clone на свой компьютер: - \begin{minted}{bash} - git clone git://github.com/my_name/cpp-notes.git - \end{minted} - \end{enumerate} - \subsection{Работа с ответвлением} - Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репрезиторий. - - - \textcolor{red}{NB}) Для тех, кому не все просто, отправляю учить \href{https://git-scm.com/book/ru/v1}{основы git} - \subsection{Залив в основной репрезиторий} - Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения предложатся создателю репрезетория. - - \subsection{Добавление изменений основного репрезитория в ваш} - Рассмотрим такую ситуацию: вы работаете над статьей. И в это время, кто-то меняет основной репрезиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? - Решение: - \begin{minted}[breaklines]{bash} - git remote add sorokin "https://github.com/sorokin/cpp-notes.git" # добавим основной репрезиторий в remote - git remote -v # убедимся, что он добавился в remote - git fetch sorokin # сделаем из него ветку - git branch -v # убедимся, что появилась новая ветка со всем нужными нами изменениями - git merge sorokin/master #сливаемся с новой веткой - git push # заливаем все на нас сервер - \end{minted} - - -\textcolor{red}{NB}) После того, как мы получили ветку основного репрезитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. - - -\textcolor{red}{NB}) Если кто не силен в ветвлении, смотрите все туже \href{https://git-scm.com/book/ru/v1}{книгу}. -\section{LaTeX} - TODO - -\subsection{Структура} - -\begin{enumerate} - \item Когда вы пишите статью, лучше всего делить ее на секции и подсекции, которые потом будут отражаться в оглавлении. Чем лучше структурирован текст, тем проще ориентироваться в оглавлении. -\end{enumerate} - -\subsection{Вставка кода} - -Для вставки исходного кода можно использовать разные средства. Я остановился на \textbf{minted}. Мне показалось, что это очень удобная и красивая библиотека. Перейдем к примерам: - - -Вставим код: -\begin{minted}{c++} - - #include - - using namespace std; - - int main() { - cout << "Hello, world!" << endl; - - return 0; - } -\end{minted} - -Хорошо теперь наведем порядок, написав преамбулу. - -\begin{minted} - [linenos, % нумерация строк - frame=lines, framesep=2mm, % черта сверху и снизу - tabsize = 4, % размер табуляции - breaklines % перенос текста - ]{c++} - - #include - - using namespace std; - - int main() { - cout << "Hello, world!" << endl; // comment - return 0; - } -\end{minted} - -Также можно вставлять код прямо в с текст \mintinline{c++}{std::shared_ptr; // smart pointer }. И я рекомендую делать именно так. - - -\textcolor{red}{NB}) Есть различные стили кода, но стандартная вроде не плоха. - -\subsection{Вставка ссылок и гиперссылок} - - \href{http://blog.harrix.org/article/661#h2_2}{Вот статья на эту тему)} - - - \url{http://www-sbras.nsc.ru/win/docs/TeX/LaTex2e/hyperref_options.pdf} - - Вот так можно вставлять ссылки. - - -Также желательно их подсветить в нормальные цвета, что я и сделал в этом документе. Но эти свойства будут прописываться в main.tex, поэтому можно об этом не волноваться. - - -\section{Советы и пожелания} - TODO - - -\end{document} From 8081924d7e2279541c436d820ef03e253c89ab7d Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Fri, 13 Oct 2017 11:05:15 +0300 Subject: [PATCH 16/30] from main branch --- compilation.tex | 34 +++++++++ how-to-contribute.tex | 173 ++++++++++++++++++++++++++++++++++++++++++ preprocessor.tex | 65 ++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 compilation.tex create mode 100644 how-to-contribute.tex create mode 100644 preprocessor.tex diff --git a/compilation.tex b/compilation.tex new file mode 100644 index 0000000..3e4320e --- /dev/null +++ b/compilation.tex @@ -0,0 +1,34 @@ +\section{Процесс компиляции программ} +Очень небольшое число программ написаны в одном файле. Исходный код большинства программ разбит на множество файлов. Например ядро Linux версии 3.2 содержит 37626 файлов. + +Программы на C++ содержат файлы двух видов, одни имеют расширение .h, другие --- .cpp. Файлы имеющие расширение .h называются хедерами (англ. header). Для получения исполняемого файла из исходного кода выполняется процесс, называемый компиляция, состоящий из следующих стадий: + +\begin{enumerate} +\item Препроцессирование --- применяется к каждому .cpp файлу. В случаях, когда результат этой операции сохраняют на диск, файлу дают расширение .i. +\item Трансляция --- применяется к результату препроцессирования, результатом этой операции является код на ассемблере. Файлы такого типа имеют расширение .s. +\item Ассемблирование --- переводит код на ассемблере в машинный код, такие файлы имеют расширение .o и называются объектными файлами. +\item Линковка --- процесс получающий на вход множество объектных файлов и выдающий на выходе единый исполняемый файл. +\end{enumerate} + +В первых компиляторах все эти стадии выполнялись отдельными программами, а результат работы этих стадий выписывался в явном виде. Сейчас, в силу разных соображений, некоторые стадии выполняются вместе, без явного выписывания промежуточного результата. Например, в подавляющем большинстве компиляторов препроцессирование и трансляция делаются вместе, без явного выписывания результата препроцессирования на диск. Это связано с тем, что как правило результат препроцессирования большой и его сериализия/десериализация создает лишние накладные расходы замедляющие компиляцию. Компилятор clang умеет работать в режиме integrated-as (встроенных ассемблер), когда результат трансляции не выписывается в виде ассемблерного текста, а сразу преобразуется в машинный код. Это позволило увеличить скорость компиляции и улучшить сообщения об ошибках при использовании asm-вставок. + +Для компиляции программ, используется команда {\bf g++}. На самом деле, программа {\bf g++} не делает ничего сама, она всего лишь вызывает другие программы в правильном порядке. Например, при вызове {\bf g++ helloworld.cpp} выполняются следующие команды: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{sh} +# препроцессирование и трансляция +cc1plus -quiet -D_GNU_SOURCE helloworld.cpp -quiet -dumpbase helloworld.cpp -mtune=generic -march=x86-64 -auxbase helloworld -version -o /tmp/ccSpnAlT.s + +# ассемблирование +as --64 -o /tmp/ccW7yOj1.o /tmp/ccSpnAlT.s + +# линковка +ld --eh-frame-hdr -m elf_x86_64 /lib/crt1.o /lib/crti.o /lib/crtbegin.o -L/lib -dynamic-linker /lib/ld-linux-x86-64.so.2 /tmp/ccW7yOj1.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /lib/crtend.o /lib/crtn.o +\end{minted} + +Команда {\bf g++}, вызывающая другие команды, называется драйвером. Как видно внутренние команды получают достаточно много опций, чтобы их вызывать напрямую. К счастью, драйвер позволяет выполнить каждую стадию компиляции отдельно: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{sh} +g++ -S -o helloworld.s helloworld.cpp # вызывает cc1plus +g++ -c -o helloworld.o helloworld.s # вызывает as +g++ helloworld.o # вызывает ld +\end{minted} diff --git a/how-to-contribute.tex b/how-to-contribute.tex new file mode 100644 index 0000000..b3ee36d --- /dev/null +++ b/how-to-contribute.tex @@ -0,0 +1,173 @@ +\documentclass[12pt]{article} + +\usepackage[utf8]{inputenc} % common +\usepackage[russian]{babel} % for russian lang + +\usepackage{graphicx} % for add picture +%\usepackage{daytime} % for displaying version number and date +\usepackage{datetime} % for current data +\usepackage{indentfirst} % Красная строка +\usepackage[usenames]{color} % for \textcolor +\usepackage{xcolor} +\usepackage{hyperref} % for link +\definecolor{linkcolor}{HTML}{799B03} % цвет ссылок +\definecolor{urlcolor}{HTML}{799B03} % цвет гиперссылок +\hypersetup{colorlinks=true, linkcolor=linkcolor, urlcolor=urlcolor,} +\usepackage{minted} % for highlight + +% seting size page +\voffset=-20mm +\textheight=220mm +\hoffset=-25mm +\textwidth=180mm + +\begin{document} + + \begin{center} + + {\Large \bf F.A.Q. для конспектов} \\ + \vspace{0.5em} + {\large Собрано {\today} в {\currenttime}} + + Эта статья призвана помочь писать конспекты и стандартизировать их написание. Я не буду рассказывать все о LaTeX. Я только опишу инструменты, которыми я советую пользоваться, и которых я надеюсь вам будет достаточно. + + + P. S. сложно добиться того, чтобы только из pdf получилась понятная статья, поэтому это статья-пример, которой следует пользоваться так: смотрим код, потом смотрим во что это компилируется. + + \end{center} +\underline{\hbox to 1\textwidth{{ } \hfil{ } \hfil{ } }} + +\tableofcontents + +\newpage + +\section{Установка LaTeX} + +\subsection{Linux} + +Вам нужно установить: +\begin{enumerate} + \item texlive-live - сам LaTeX + \item minted - для вставки исходного кода + \item pygment - для minted +\end{enumerate} + + +\textcolor{red}{NB}) Для сборки tex-файла нужно указывать в командной строке флаг \textbf(--shell-escape) + + +\textcolor{red}{NB}) \href{http://tug.ctan.org/macros/latex/contrib/minted/minted.pdf}{Документация для minted} + +\subsection{Win} + TODO +\section{Работа с Git} +Для того, чтобы работать над конспектом необходимо использовать git. Поэтому я привел несколько ситуации, в которые вы неизбежно попадете. + + +{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной репрезиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной репрезиторий ({\bf pull request}). + \subsection{Начало работы с основным репрезиторем} + \begin{enumerate} + \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репрезитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) + + + Теперь ответвление репрезитория будет у вас на гите. Вы просто работает с ним как со своим. + \item делаем clone на свой компьютер: + \begin{minted}{bash} + git clone git://github.com/my_name/cpp-notes.git + \end{minted} + \end{enumerate} + \subsection{Работа с ответвлением} + Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репрезиторий. + + + \textcolor{red}{NB}) Для тех, кому не все просто, отправляю учить \href{https://git-scm.com/book/ru/v1}{основы git} + \subsection{Залив в основной репрезиторий} + Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения предложатся создателю репрезетория. + + \subsection{Добавление изменений основного репрезитория в ваш} + Рассмотрим такую ситуацию: вы работаете над статьей. И в это время, кто-то меняет основной репрезиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? + Решение: + \begin{minted}[breaklines]{bash} + git remote add sorokin "https://github.com/sorokin/cpp-notes.git" # добавим основной репрезиторий в remote + git remote -v # убедимся, что он добавился в remote + git fetch sorokin # сделаем из него ветку + git branch -v # убедимся, что появилась новая ветка со всем нужными нами изменениями + git merge sorokin/master #сливаемся с новой веткой + git push # заливаем все на нас сервер + \end{minted} + + +\textcolor{red}{NB}) После того, как мы получили ветку основного репрезитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. + + +\textcolor{red}{NB}) Если кто не силен в ветвлении, смотрите все туже \href{https://git-scm.com/book/ru/v1}{книгу}. +\section{LaTeX} + TODO + +\subsection{Структура} + +\begin{enumerate} + \item Когда вы пишите статью, лучше всего делить ее на секции и подсекции, которые потом будут отражаться в оглавлении. Чем лучше структурирован текст, тем проще ориентироваться в оглавлении. +\end{enumerate} + +\subsection{Вставка кода} + +Для вставки исходного кода можно использовать разные средства. Я остановился на \textbf{minted}. Мне показалось, что это очень удобная и красивая библиотека. Перейдем к примерам: + + +Вставим код: +\begin{minted}{c++} + + #include + + using namespace std; + + int main() { + cout << "Hello, world!" << endl; + + return 0; + } +\end{minted} + +Хорошо теперь наведем порядок, написав преамбулу. + +\begin{minted} + [linenos, % нумерация строк + frame=lines, framesep=2mm, % черта сверху и снизу + tabsize = 4, % размер табуляции + breaklines % перенос текста + ]{c++} + + #include + + using namespace std; + + int main() { + cout << "Hello, world!" << endl; // comment + return 0; + } +\end{minted} + +Также можно вставлять код прямо в с текст \mintinline{c++}{std::shared_ptr; // smart pointer }. И я рекомендую делать именно так. + + +\textcolor{red}{NB}) Есть различные стили кода, но стандартная вроде не плоха. + +\subsection{Вставка ссылок и гиперссылок} + + \href{http://blog.harrix.org/article/661#h2_2}{Вот статья на эту тему)} + + + \url{http://www-sbras.nsc.ru/win/docs/TeX/LaTex2e/hyperref_options.pdf} + + Вот так можно вставлять ссылки. + + +Также желательно их подсветить в нормальные цвета, что я и сделал в этом документе. Но эти свойства будут прописываться в main.tex, поэтому можно об этом не волноваться. + + +\section{Советы и пожелания} + TODO + + +\end{document} diff --git a/preprocessor.tex b/preprocessor.tex new file mode 100644 index 0000000..aa8cfec --- /dev/null +++ b/preprocessor.tex @@ -0,0 +1,65 @@ +\section{Препроцессирование} + +Препроцессирование получает на вход .cpp файл и выдает на выход .i файл. Препроцессирование выполняется программой называемой препроцессор. + +Препроцессор проходит по входному файлу, копируя текст в выходной файл. Если он встречает строку начинающуются с символа {\bf \#}, он выполняет команду, которая записана после {\bf \#}. Такие команды называются директивами препроцессора. + +Примером директивы препроцессора является директива {\bf \#include}. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +#include "somefile.h" +\end{minted} + +Когда препроцесор встречает эту директиву, он подставляет на её место текст файла {\bf somefile.h}. При этом текст файла {\bf somefile.h} тоже препроцессируется. Если в нём есть какие-то директивы то они будут выполнены. + +Пусть даны следующие файлы +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +// print.h +void print(char const*); + +// main.cpp +#include "print.h" + +int main() +{ + print("Hello, world"); +} +\end{minted} +Результатом препроцессирования {\bf main.cpp} будет: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void print(char const*); + +int main() +{ + print("Hello, world"); +} +\end{minted} +В этом можно убедиться выполним команду {\bf g++ -E main.cpp}. Ключ {\bf -E} говорит драйверу, что нужно лишь отпрепроцессировать входной файл. + +Директива {\bf \#include} имеет две формы +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +#include +#include "somefile.h" +\end{minted} + +Первая форма ищет файл {\bf somefile.h} в списке каталогов, называемых include directories. Их можно указать компилятору опцией {\bf -I}. Например: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{sh} +g++ -Isome/directory -Ianother/directory -I../and/another/one myfile.cpp +\end{minted} +Файлы ищутся в этих директориях слева направо. Препроцессор автоматически добавляет в конец этого списка пути к директориям в которых лежат хедера стандартной библиотеки. + +Вторая форма директивы {\bf \#include} ищет {\bf somefile.h} сначала в текущем каталоге, а потом в include directories. + +В стандарте определены следующие директивы препроцессора: +\begin{enumerate} +\item \#define +\item \#elif +\item \#else +\item \#error +\item \#endif +\item \#if +\item \#ifdef +\item \#ifndef +\item \#include +\item \#pragma +\item \#undef +\end{enumerate} From 6c86d63882bf10d35b97854a8dba3c9073aa0973 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Sat, 11 Nov 2017 03:59:14 +0300 Subject: [PATCH 17/30] add notes of exception and RAII --- Exceptions/Exception | 189 ++++++++++++++++++ Exceptions/Exception safety and RAII | 183 +++++++++++++++++ ...6\320\277\321\200\320\276\321\201\321\213" | 35 ++++ 3 files changed, 407 insertions(+) create mode 100644 Exceptions/Exception create mode 100644 Exceptions/Exception safety and RAII create mode 100644 "Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" diff --git a/Exceptions/Exception b/Exceptions/Exception new file mode 100644 index 0000000..065c23b --- /dev/null +++ b/Exceptions/Exception @@ -0,0 +1,189 @@ +План: +1) основы механизма +2) тонкости использования +3) Практика: Зачем это все собственно нужно? +--------------------------------------------------------------------------------------- +1) основы механизма + +В ходе работы программы часто могут возникнуть ситуации, которые не вписываются в общий алгоритм, или которые просто не отражают саму логику программы, но их тоже приходиться учитывать. Это, например, нехватка памяти, неправильный вывод пользователя, ошибка работы с ресурсами и т. д. С другой стороны, четко определить, что стоит отделять от основной логики сложно. Поэтому дадим пока довольно общие определения, а как этим пользоваться на практике мы обсудим отдельно. + +Исключение - это ситуация, которую МЫ сочли исключительной. Эти ситуации мы будет обрабатывать как-то особенно(отдельно от основной логики). Теперь нам нужно научиться выявлять, отслеживать и обрабатывать их. + +Пусть мы пишем функцию, которая принимает три числа кладет в первое результат деления второго на третие. +void div(int &res, int a, int b) { + res = a / b; +} + +Пусть исключительно ситуация возникает тогда, когда b = 0. И мы хотим обработать это исключение так: положить в res просто значение a. + +void div(int &res, int a, int b) { + try { + if (b == 0) + throw b; // генерируем исключение + // если исключения нет, то код работает в обычном режиме + res = a / b; + } + catch(int a) { // ловим исключение + res = a; // обрабатываем исключение + } +} +NB) типом исключения называется тип объекта, которым передаем в throw. Тип исключения должен ясно описывать ситуацию, так как по типу выбирается обработчик. + +Теперь рассмотрим используемы здесь конструкции: + +try{} -- защищенный блок. Здесь мы пишем код, в которым будут ловиться исключения. +catch(){} -- блок перехвата исключений. Здесь буду ловиться исключения указанные в (), и обрабатывать инструкциями в {} +throw -- оператор инициализации исключения. Он генерирует исключение. (Обычно говорят, "бросает" или "выбрасывает" исключение) + +блок try-catch - реализует обработку исключений. И имеет общий вид: +try {операторы защищенного блока} +// catch-блоки +catch() {обработчик} +... +catch() {обработчик} + +Давайте еще раз как, это работает: если нет исключения, то все работает как обычно и catch-блоки не выполняются. Если происходит выброс исключения. Создается объект типа int со значением b, потом происходит выбор нужного обработчика, по типу этого объекта. Когда он нашелся, то выполняется соответствующий код обработки. + +Теперь более подробно рассмотрим этот механизм: + +общий вид catch-блока: +1) catch(Type) {обработчик исключения } // мы не используем генерируемый исключением объект. +2) catch(Type exception_variable) { обработчик исключения } +3) catch(...) {обработчик исключения} // мы ловим исключение с любым типом объекта + +Что происходит когда мы генерируем исключение: +1) Создается статическая переменная со значением, заданным в операторе я throw. Она будет существовать до тех пор, пока исключение не будет обработано. Если переменная является объектом класса, то при создании вызовется конструктор копирования. +2) Прерывается выполнение защищенного try-блока(вызываются деструкторы временных объектов). +3) Выполняется поиск первого подходящего catch-блока + +Как происходит поиск catch-блока: +Блоки рассматриваются в том порядке, в котором они указанны. По следующем критериям: +1) если тип, указанный в catch-блоке, совпадает с типом исключения или является ссылкой на этот тип. +2) класс, заданный в catch-блоке, является предком класса, заданного в throw, и наследование открытое (public). +3) указатель, заданый в операторе throw, может быть преобразован по стандартным правилам к указателю, заданному в catch-блоке. +4) в catch-блоке указанно многоточие + +Если найдет нужный блок обработки, то выполняется его код, а остальные catch-блоки игнорируются. + +NB) Так как поиск ведется последовательно, то нужно учитывать порядок catch-блоков (Например, catch(...) должен быть последним). + +Если ни один catch-блок не подошел, то исключение не обработано, и поиск продолжается во внешнем коде (внешний блок или вызывающая функция). + +Если пройдя по цепочке вызовов функции мы не нашли ни одного подходящего обработчика, то вызывается функция terminate(), которая вызывает abort(). +NB) таком случае единственное, что мы модем сделать это написать свою функцию terminate() и зарегистрировать ее с помощью set_terminate. +Пример: +void my_terminate() { cout << "It's not a bug, it's a feature!"} +set_terminate(my_teminate); + +Так же мы можем после обработки исключение в конце catch-блока написать оператор throw без параметров, тогда исключение считается обработанным не до конца. И происходит повторный выброс исключения: по статическому объекту исключения ищется еще один обработчик, который лежит выше по цепочке вызовов. +NB) при это следует заметить, что при повторном выбросе исключения рассматривается не параметр текущего catch-блока, а именно изначальный статический объект. Именно он скопируется в качестве параметра в следующий обработчик. Поэтому его этот статический объект живет пока его исключение не обработается полностью. +NB NB) Поэтому при приведении тип исключения не теряется. + +Пусть мы бросили исключение Exception_heir, которое наследуется от Exception_base. +Следует различать catch(Exception_base exc) и сatch(Exception_base &exc) +В первом случае exc -- это копия базовой части статического объекта типа Exception_heir. +А во втором сработает динамическое связывание, и мы может использовать виртуальные функции, чего не произойдет в первом случае, не смотря на идентичность статических типов exc. + +Еще одним интересным моментом являются try-блоки в конструкторе. Конструктор - это очень важная часть класса, поэтому возможно ситуация, когда мы хотим обработать все исключения в нем. Наверно мы напишем так: +class St { +public: + St(): member() { + try { + // Constructor's code + } + {catchs} + } +private: + Member_type member; +} +Но заметим, что инициализация мемберов не находится внутри try-блока и не исключения возникшие в их конструкторах не поймаются. На самом деле тут есть особый синтаксис: +class St { +public: + St() try: member() { + { + // Constructor's code + } + {catchs} + } +private: + Member_type member; +} + +NB) Это называется функциональные try-блоки, то есть те, которые оборачивают все тело функции. Они есть и для обычных функций. +int main() try { + //some code +} +{catchs} +///////////////////NOTE/////////////////////////////// +У меня здесь почему-то не заработало, вот код: +#include + +class St { +public: + St(int a, int b, int c) try: a(a), b(b), c(c) { + throw 1; + } + catch(...) { + std::cout << "error!"; + } +private: + int a, b, c; +}; + +int main () { + St a(1, 2, 3); + return 0; +} +выводится строка "error!", но после этого дамп по памяти. +///////////////////NOTE/////////////////////////////// + +Также важно помнить, что если в конструкторе происходит исключение, то для него не вызовется деструктор, так объект еще не считается созданным. + +NB) При генерации исключения параметр throw копируется в статическую переменную того же типа, что и СТАТИЧЕСКИЙ тип параметр. То есть такой код работать не бдует: + +try { + Exception_base *exc = new Exception_heir(); + throw exc; +} catch (Exception_heir *exc) { + //some code +} + +2) Best practice + +Теперь, когда мы разобрались как работает механизм исключений, не плохо понять когда и где его лучше использовать. + +Я опишу несколько ситуаций, которые покажут плохие и хорошие стороны исключений, и постараюсь подвести итог в конце. + +Минусы: +1) Иногда бывает сложно понять, где происходит исключение и как его следует обработать. Эти ситуации возникают, когда нам приходится обрабатывать исключения, приходящие нам от внешнего кода. + + Во-первых без документации невозможно понять какие функции бросают исключения и какого типа. + + Во-вторых когда, мы узнаем тип исключения и какая функция его бросает, не всегда по типу исключения очевидно, как его следует обработать. + + real story: Бывает, что вы используете библиотеку с закрытым исходным кодом. И неожиданно одна из функции библиотеки кидает нам исключения типа int. И как его следует обработать ? :( + +2) Не всегда мы знаем, как нужно обрабатывать ошибки, поэтому иногда больший смысл имеет просто логировать ошибки и просто продолжать исполнение кода, игнорируя ошибку. Это можно сделать catch(...), но если в нашем коде это распространенная ситуация, то механизм исключений избыточен. + +Плюсы: +Исключения нужны там, где мы хотим, чтобы программа всегда работала корректно. +1) Например, мы увеличиваем вектор, и если у нас кончилась память для resize, то программа не падает и не убивает те данные, которые уже лежат в векторе. +2) Это механизм очень удобный, когда мы организовываем исключения в иерархии классов. Создав такую структуры мы при обработке, может обрабатывать как более общие ошибки, так и более специализированные. +Пример: +class StackException {}; +class popOnEmpty(): public StackException {}; +class pushOnFull(): public StackException {}; + +Причем сгенерировав исключение типа popOnEmpty() +Мы можем в разных обработчиках независимо выбирать: обработать как popOnEmpty или как StackException, так как тип исключения не теряется при повторной генерации этого исключения. + +3)Полезно знать про стандартные исключения, которые также связанны наследованием. +|) std::ecxeption + | | + v v +||) std::bad_alloc, std::bad_cast, std::bad_typeid и т. д. + +Подробнее можно почитать здесь: https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm +http://en.cppreference.com/w/cpp/error/exception + +4) Хорошим тоном является наследование от std::exception diff --git a/Exceptions/Exception safety and RAII b/Exceptions/Exception safety and RAII new file mode 100644 index 0000000..0552621 --- /dev/null +++ b/Exceptions/Exception safety and RAII @@ -0,0 +1,183 @@ +Exception safety + +Механизм исключений является довольно мощным инструментом. Но при этом не редко возникает проблемы с его читаемостью. Далеко не всегда очевидно откуда пришло исключение и как стоит его обрабатывать. Некоторые сравнивают эффект вызова исключений с эффектами от вызова goto. + +Поэтому относительно работы исключений с объектами существуют Гарантии безопасности исключений (Exception safety). Это ряд уровней безопасности, которые присваиваются методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. + +Уровни гарантий: + +1) "No guarantees" - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что объект больше не будет использоваться. +2) "Basic guarantees" - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. +3) "Strong guarantees" - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. +3) "Nothrow guarantees" - Кроме базовой гарантии, гарантируется, что исключения не возникают. + + +Теперь давайте более подробно: + +Гарантия nothrow: + +Она есть у очень небольшого количества функции: swap, vector::pop_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. + +Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. + +NB) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. +Подробнее в статье про перемещение. + +Примеры нарушения базовой гарантии: +Главным способом предотвращением утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). + + Offtops: + +1) Можно попросить оператор new не кидать исключение с помощью константы std::nothrow + +2) Можно указать какие исключение может кидать функция: +void f() throw(int, double)* +*что здесь можно написать: +1) throw() - функция не кидает исключения +2) throw(T1, T2, T3) - функция может кидать исключения T1, T2, T3 +3) throw(...) - функция может кидать любые исключения +Это не используется в c++11, так как возникли какие-то проблемы, и по факту полезным оказался только throw(). + +RAII-class: +Основы: +"Resource Acquision is Initialozation" или "Захват ресурса - это инициализация"- это идиома класса, который инкапсулирует управление каким-то ресурсом. Она значит, что объект этого класса, получает доступ к ресурсу и удерживает его в течении своей жизни, а потом этот ресурс высвобождается. +В конструкторе он должен захватить ресурс(открыть файл, выделить файл и т. д.), а в десткрукторе освободить его(закрыть файл, освободить память и т. д.). + +Также важно подумать, что должно происходить при копировании объекта, часто мы просто явно запрещаем это делать. + +Что это дает? +1) Удобство кода: нам не приходится каждый раз в конце тела функции освобождать ресурсы. Мы просто заводим локальную переменную. И когда выполнение текущего блока будет завершено, локальные объекты RAII-классов удалятся и ресурсы освободятся автоматически. +2) Безопасность исключений: Если вызывается исключение, то нам гарантируется, что стек очиститься и все локальные объекты удаляться, а значит и освободятся ресурсы. Без RAII приходится в ручную освобождать все ресурсы. Причем нужно учитывать какие ресурсы успели захватить до исключения, а какие нет. +3) Часто важно освобождать ресурсы в обратном порядке, относительно того, как они были захвачены. Как раз раскрутка стека при удалении локальных объектов, это поддерживает. + +NB) Это идиома работает не только в С++, а любом языке с предсказуемым временем жизни объектов. + +Вот не большой пример RAII-класса, который будет управлять файлом. +Ресурсом является данные типа FILE (формат файла в Си). + +class File { +public: + File(char const *filename, char const *mode) + : _file(fopen(filename, mode)) { } //захват ресурса + + ~File() { + fclose(_file); // освобождение ресурса + } + + File(File const &) = delete; + File operator=(File const &) = delete; + +private: + FILE *_file; +} + +Тонкие моменты в RAII: + +1) Необрабатываемые исключения +Вот код: + +int main { + File("input.txt", "r"); + // здесь происходит исключение + return 0; +} + +Что не так? +Проблема в том, что не смотря на то, что мы использовали RAII-класс, ресурс не будет освобожден, так как удаление локальных файлов, гарантируется, только при перехвате исключения, иначе поведение зависит от реализации. +Поэтому main следует написать так: +int main() { + try { + File("input.txt", "r"); + // здесь происходит исключение + } + catch(...) { + cerr << "Unexpected exception.\n"; + } +} +Теперь все ок. + +2) Исключение в деструкторе. Но это проблема сама по себе. + +3) Исключения в конструкторах. + +Вернемся к предыдущему примеру File. +Пусть в конструкторе есть код, который может сгенерить исключение, тогда возникает проблема освобождения ресурса. +File::File(char const *filename, char const *mode) + : _file(fopen(filename, mode)) { + //код допускающий исключение + } +Если в теле конструктора происходит исключение, то деструктор не вызовется, так как объект не считается созданным. Что делать? + +Решение № 1 +Написать try-catch +class File { +public: + File(char const *filename, char const *mode) + try + : _file(fopen(filename, mode)) { + //код допускающий исключение + } + catch (...) { + destruct_obj(); + } + + ~File() { + destruct_obj(); + } + +private: + void destruct_obj() { + fclose(_file); + } + + FILE * _file; +}; +Минусы решения: можно лучше. +Решение № 2 +Можно сделать отдельный подкласс, который хранит в себе ресурс. +class File { + struct FileHandle { + FileHandle(FILE *fh) + : _fh(fh) { } + + ~FileHandle() { + fclose(_fh); + } + + FILE *_fh; + } + +public: + File(char const * filename, char const * mode) + : _file(fopen(filename, mode)) { + // код допускающий исключения + } + + ~File() = default; + +private: + FileHandle _file; +}; +Теперь все тоже ок, так как при возникновении исключения, вызовутся деструкторы от все мемберов. +Минусы решения: можно еще лучше +Решение № 3 (Изящное) +Делегирующий конструктор. +class File +{ + File(FILE * file) + : _file(file) { } + +public: + File(char const * filename, char const * mode) + : File(fopen(filename, mode)) { + // код допускающий исключения + } + + ~File() { + fclose(_file); + } + +private: + FILE *_file; +}; +Дело в том, что если мы в конструкторе вызываем другой конструктор, то после его выполнения объект считается созданным и уничтожится в нужное время. diff --git "a/Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" "b/Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" new file mode 100644 index 0000000..6256f87 --- /dev/null +++ "b/Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" @@ -0,0 +1,35 @@ +On 11/02/2017 11:40 PM, Павел Головин wrote: +> Здравствуйте, я начал писать конспект и у меня возник вопрос по поводу +> Excetions: +> +> Нужно ли писать про реализацию исключений, там про всякие прерывания +> и т. д. ? + +Я считаю, что нет. Сама по себе тема эта интересная, но достаточно +объемная. Ты сам видишь размер статьи про SEH. И рассказ про то как +работают исключения в Линуксе такой же объемный будет. + +Я думаю, если бы у нас уже был готовый конспект, то это была бы хорошая +advanced-тема. + +> Вопрос возник из-за того, что я нашел на сайте лекций вот эту ссылку: +> http://sorokin.github.io/cpp-course/seh/seh.html +> +> План пока такой: +> +> 1) описать сам механизм, типа: напишем так будет то-то. +> 2) Постараться объяснить где его следует использовать. + +Да, согласен. В принципе, я когда рассказываю его на лекции я +рассказываю по такому же плану. + +1) Существует try, существует catch, работают так. +2) И дальше пошли разнообразные best practices. + +Я правда часто начинаю рассказ с того, что было до этого (а ля +error-codes, goto cleanup, etc) и их недостатки, но в конспекте это пока +не нужно, а если и нужно то лучше наверное отдельной главой. + +10.11.2017 Вопросы: +1) Что произойдет если во время исключения броситься исключение? +2) Есть ли рекомендации к гарантиям конструкторов? From ed193784255c6184e5a4ac48a5dddb9a71d068ad Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Sat, 11 Nov 2017 04:01:06 +0300 Subject: [PATCH 18/30] ind --- ...5\321\201\320\277\320\265\320\272\321\202" | 374 ++++++++++++++++++ ...1\320\277\320\265\320\272\321\202\320\260" | 42 ++ 2 files changed, 416 insertions(+) create mode 100644 "Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" create mode 100644 "Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" diff --git "a/Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" "b/Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" new file mode 100644 index 0000000..69ca96c --- /dev/null +++ "b/Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" @@ -0,0 +1,374 @@ +План: +1) основы механизма +2) тонкости использования +3) Практика: Зачем это все собственно нужно? +--------------------------------------------------------------------------------------- +1) основы механизма + +В ходе работы программы часто могут возникнуть ситуации, которые не вписываются в общий алгоритм, или которые просто не отражают саму логику программы, но их тоже приходиться учитывать. Это, например, нехватка памяти, неправильный вывод пользователя, ошибка работы с ресурсами и т. д. С другой стороны, четко определить, что стоит отделять от основной логики сложно. Поэтому дадим пока довольно общие определения, а как этим пользоваться на практике мы обсудим отдельно. + +Исключение - это ситуация, которую МЫ сочли исключительной. Эти ситуации мы будет обрабатывать как-то особенно(отдельно от основной логики). Теперь нам нужно научиться выявлять, отслеживать и обрабатывать их. + +Пусть мы пишем функцию, которая принимает три числа кладет в первое результат деления второго на третие. +void div(int &res, int a, int b) { + res = a / b; +} + +Пусть исключительно ситуация возникает тогда, когда b = 0. И мы хотим обработать это исключение так: положить в res просто значение a. + +void div(int &res, int a, int b) { + try { + if (b == 0) + throw b; // генерируем исключение + // если исключения нет, то код работает в обычном режиме + res = a / b; + } + catch(int a) { // ловим исключение + res = a; // обрабатываем исключение + } +} +NB) типом исключения называется тип объекта, которым передаем в throw. Тип исключения должен ясно описывать ситуацию, так как по типу выбирается обработчик. + +Теперь рассмотрим используемы здесь конструкции: + +try{} -- защищенный блок. Здесь мы пишем код, в которым будут ловиться исключения. +catch(){} -- блок перехвата исключений. Здесь буду ловиться исключения указанные в (), и обрабатывать инструкциями в {} +throw -- оператор инициализации исключения. Он генерирует исключение. (Обычно говорят, "бросает" или "выбрасывает" исключение) + +блок try-catch - реализует обработку исключений. И имеет общий вид: +try {операторы защищенного блока} +// catch-блоки +catch() {обработчик} +... +catch() {обработчик} + +Давайте еще раз как, это работает: если нет исключения, то все работает как обычно и catch-блоки не выполняются. Если происходит выброс исключения. Создается объект типа int со значением b, потом происходит выбор нужного обработчика, по типу этого объекта. Когда он нашелся, то выполняется соответствующий код обработки. + +Теперь более подробно рассмотрим этот механизм: + +общий вид catch-блока: +1) catch(Type) {обработчик исключения } // мы не используем генерируемый исключением объект. +2) catch(Type exception_variable) { обработчик исключения } +3) catch(...) {обработчик исключения} // мы ловим исключение с любым типом объекта + +Что происходит когда мы генерируем исключение: +1) Создается статическая переменная со значением, заданным в операторе я throw. Она будет существовать до тех пор, пока исключение не будет обработано. Если переменная является объектом класса, то при создании вызовется конструктор копирования. +2) Прерывается выполнение защищенного try-блока(вызываются деструкторы временных объектов). +3) Выполняется поиск первого подходящего catch-блока + +Как происходит поиск catch-блока: +Блоки рассматриваются в том порядке, в котором они указанны. По следующем критериям: +1) если тип, указанный в catch-блоке, совпадает с типом исключения или является ссылкой на этот тип. +2) класс, заданный в catch-блоке, является предком класса, заданного в throw, и наследование открытое (public). +3) указатель, заданый в операторе throw, может быть преобразован по стандартным правилам к указателю, заданному в catch-блоке. +4) в catch-блоке указанно многоточие + +Если найдет нужный блок обработки, то выполняется его код, а остальные catch-блоки игнорируются. + +NB) Так как поиск ведется последовательно, то нужно учитывать порядок catch-блоков (Например, catch(...) должен быть последним). + +Если ни один catch-блок не подошел, то исключение не обработано, и поиск продолжается во внешнем коде (внешний блок или вызывающая функция). + +Если пройдя по цепочке вызовов функции мы не нашли ни одного подходящего обработчика, то вызывается функция terminate(), которая вызывает abort(). +NB) таком случае единственное, что мы модем сделать это написать свою функцию terminate() и зарегистрировать ее с помощью set_terminate. +Пример: +void my_terminate() { cout << "It's not a bug, it's a feature!"} +set_terminate(my_teminate); + +Так же мы можем после обработки исключение в конце catch-блока написать оператор throw без параметров, тогда исключение считается обработанным не до конца. И происходит повторный выброс исключения: по статическому объекту исключения ищется еще один обработчик, который лежит выше по цепочке вызовов. +NB) при это следует заметить, что при повторном выбросе исключения рассматривается не параметр текущего catch-блока, а именно изначальный статический объект. Именно он скопируется в качестве параметра в следующий обработчик. Поэтому его этот статический объект живет пока его исключение не обработается полностью. +NB NB) Поэтому при приведении тип исключения не теряется. + +Пусть мы бросили исключение Exception_heir, которое наследуется от Exception_base. +Следует различать catch(Exception_base exc) и сatch(Exception_base &exc) +В первом случае exc -- это копия базовой части статического объекта типа Exception_heir. +А во втором сработает динамическое связывание, и мы может использовать виртуальные функции, чего не произойдет в первом случае, не смотря на идентичность статических типов exc. + +Еще одним интересным моментом являются try-блоки в конструкторе. Конструктор - это очень важная часть класса, поэтому возможно ситуация, когда мы хотим обработать все исключения в нем. Наверно мы напишем так: +class St { +public: + St(): member() { + try { + // Constructor's code + } + {catchs} + } +private: + Member_type member; +} +Но заметим, что инициализация мемберов не находится внутри try-блока и не исключения возникшие в их конструкторах не поймаются. На самом деле тут есть особый синтаксис: +class St { +public: + St() try: member() { + { + // Constructor's code + } + {catchs} + } +private: + Member_type member; +} + +NB) Это называется функциональные try-блоки, то есть те, которые оборачивают все тело функции. Они есть и для обычных функций. +int main() try { + //some code +} +{catchs} +///////////////////NOTE/////////////////////////////// +У меня здесь почему-то не заработало, вот код: +#include + +class St { +public: + St(int a, int b, int c) try: a(a), b(b), c(c) { + throw 1; + } + catch(...) { + std::cout << "error!"; + } +private: + int a, b, c; +}; + +int main () { + St a(1, 2, 3); + return 0; +} +выводится строка "error!", но после этого дамп по памяти. +///////////////////NOTE/////////////////////////////// + +Также важно помнить, что если в конструкторе происходит исключение, то для него не вызовется деструктор, так объект еще не считается созданным. + +NB) При генерации исключения параметр throw копируется в статическую переменную того же типа, что и СТАТИЧЕСКИЙ тип параметр. То есть такой код работать не бдует: + +try { + Exception_base *exc = new Exception_heir(); + throw exc; +} catch (Exception_heir *exc) { + //some code +} + +2) Best practice + +Теперь, когда мы разобрались как работает механизм исключений, не плохо понять когда и где его лучше использовать. + +Я опишу несколько ситуаций, которые покажут плохие и хорошие стороны исключений, и постараюсь подвести итог в конце. + +Минусы: +1) Иногда бывает сложно понять, где происходит исключение и как его следует обработать. Эти ситуации возникают, когда нам приходится обрабатывать исключения, приходящие нам от внешнего кода. + + Во-первых без документации невозможно понять какие функции бросают исключения и какого типа. + + Во-вторых когда, мы узнаем тип исключения и какая функция его бросает, не всегда по типу исключения очевидно, как его следует обработать. + + real story: Бывает, что вы используете библиотеку с закрытым исходным кодом. И неожиданно одна из функции библиотеки кидает нам исключения типа int. И как его следует обработать ? :( + +2) Не всегда мы знаем, как нужно обрабатывать ошибки, поэтому иногда больший смысл имеет просто логировать ошибки и просто продолжать исполнение кода, игнорируя ошибку. Это можно сделать catch(...), но если в нашем коде это распространенная ситуация, то механизм исключений избыточен. + +Плюсы: +Исключения нужны там, где мы хотим, чтобы программа всегда работала корректно. +1) Например, мы увеличиваем вектор, и если у нас кончилась память для resize, то программа не падает и не убивает те данные, которые уже лежат в векторе. +2) Это механизм очень удобный, когда мы организовываем исключения в иерархии классов. Создав такую структуры мы при обработке, может обрабатывать как более общие ошибки, так и более специализированные. +Пример: +class StackException {}; +class popOnEmpty(): public StackException {}; +class pushOnFull(): public StackException {}; + +Причем сгенерировав исключение типа popOnEmpty() +Мы можем в разных обработчиках независимо выбирать: обработать как popOnEmpty или как StackException, так как тип исключения не теряется при повторной генерации этого исключения. + +3)Полезно знать про стандартные исключения, которые также связанны наследованием. +|) std::ecxeption + | | + v v +||) std::bad_alloc, std::bad_cast, std::bad_typeid и т. д. + +Подробнее можно почитать здесь: https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm +http://en.cppreference.com/w/cpp/error/exception + +4) Хорошим тоном является наследование от std::exception + + +Exception safety + +Механизм исключений является довольно мощным инструментом. Но при этом не редко возникает проблемы с его читаемостью. Далеко не всегда очевидно откуда пришло исключение и как стоит его обрабатывать. Некоторые сравнивают эффект вызова исключений с эффектами от вызова goto. + +Поэтому относительно работы исключений с объектами существуют Гарантии безопасности исключений (Exception safety). Это ряд уровней безопасности, которые присваиваются методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. + +Уровни гарантий: + +1) "No guarantees" - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что объект больше не будет использоваться. +2) "Basic guarantees" - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. +3) "Strong guarantees" - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. +3) "Nothrow guarantees" - Кроме базовой гарантии, гарантируется, что исключения не возникают. + + +Теперь давайте более подробно: + +Гарантия nothrow: + +Она есть у очень небольшого количества функции: swap, vector::pop_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. + +Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. + +NB) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. +Подробнее в статье про перемещение. + +Примеры нарушения базовой гарантии: +Главным способом предотвращением утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). + + Offtops: + +1) Можно попросить оператор new не кидать исключение с помощью константы std::nothrow + +2) Можно указать какие исключение может кидать функция: +void f() throw(int, double)* +*что здесь можно написать: +1) throw() - функция не кидает исключения +2) throw(T1, T2, T3) - функция может кидать исключения T1, T2, T3 +3) throw(...) - функция может кидать любые исключения +Это не используется в c++11, так как возникли какие-то проблемы, и по факту полезным оказался только throw(). + +RAII-class: +Основы: +"Resource Acquision is Initialozation" или "Захват ресурса - это инициализация"- это идиома класса, который инкапсулирует управление каким-то ресурсом. Она значит, что объект этого класса, получает доступ к ресурсу и удерживает его в течении своей жизни, а потом этот ресурс высвобождается. +В конструкторе он должен захватить ресурс(открыть файл, выделить файл и т. д.), а в десткрукторе освободить его(закрыть файл, освободить память и т. д.). + +Также важно подумать, что должно происходить при копировании объекта, часто мы просто явно запрещаем это делать. + +Что это дает? +1) Удобство кода: нам не приходится каждый раз в конце тела функции освобождать ресурсы. Мы просто заводим локальную переменную. И когда выполнение текущего блока будет завершено, локальные объекты RAII-классов удалятся и ресурсы освободятся автоматически. +2) Безопасность исключений: Если вызывается исключение, то нам гарантируется, что стек очиститься и все локальные объекты удаляться, а значит и освободятся ресурсы. Без RAII приходится в ручную освобождать все ресурсы. Причем нужно учитывать какие ресурсы успели захватить до исключения, а какие нет. +3) Часто важно освобождать ресурсы в обратном порядке, относительно того, как они были захвачены. Как раз раскрутка стека при удалении локальных объектов, это поддерживает. + +NB) Это идиома работает не только в С++, а любом языке с предсказуемым временем жизни объектов. + +Вот не большой пример RAII-класса, который будет управлять файлом. +Ресурсом является данные типа FILE (формат файла в Си). + +class File { +public: + File(char const *filename, char const *mode) + : _file(fopen(filename, mode)) { } //захват ресурса + + ~File() { + fclose(_file); // освобождение ресурса + } + + File(File const &) = delete; + File operator=(File const &) = delete; + +private: + FILE *_file; +} + +Тонкие моменты в RAII: + +1) Необрабатываемые исключения +Вот код: + +int main { + File("input.txt", "r"); + // здесь происходит исключение + return 0; +} + +Что не так? +Проблема в том, что не смотря на то, что мы использовали RAII-класс, ресурс не будет освобожден, так как удаление локальных файлов, гарантируется, только при перехвате исключения, иначе поведение зависит от реализации. +Поэтому main следует написать так: +int main() { + try { + File("input.txt", "r"); + // здесь происходит исключение + } + catch(...) { + cerr << "Unexpected exception.\n"; + } +} +Теперь все ок. + +2) Исключение в деструкторе. Но это проблема сама по себе. + +3) Исключения в конструкторах. + +Вернемся к предыдущему примеру File. +Пусть в конструкторе есть код, который может сгенерить исключение, тогда возникает проблема освобождения ресурса. +File::File(char const *filename, char const *mode) + : _file(fopen(filename, mode)) { + //код допускающий исключение + } +Если в теле конструктора происходит исключение, то деструктор не вызовется, так как объект не считается созданным. Что делать? + +Решение № 1 +Написать try-catch +class File { +public: + File(char const *filename, char const *mode) + try + : _file(fopen(filename, mode)) { + //код допускающий исключение + } + catch (...) { + destruct_obj(); + } + + ~File() { + destruct_obj(); + } + +private: + void destruct_obj() { + fclose(_file); + } + + FILE * _file; +}; +Минусы решения: можно лучше. +Решение № 2 +Можно сделать отдельный подкласс, который хранит в себе ресурс. +class File { + struct FileHandle { + FileHandle(FILE *fh) + : _fh(fh) { } + + ~FileHandle() { + fclose(_fh); + } + + FILE *_fh; + } + +public: + File(char const * filename, char const * mode) + : _file(fopen(filename, mode)) { + // код допускающий исключения + } + + ~File() = default; + +private: + FileHandle _file; +}; +Теперь все тоже ок, так как при возникновении исключения, вызовутся деструкторы от все мемберов. +Минусы решения: можно еще лучше +Решение № 3 (Изящное) +Делегирующий конструктор. +class File +{ + File(FILE * file) + : _file(file) { } + +public: + File(char const * filename, char const * mode) + : File(fopen(filename, mode)) { + // код допускающий исключения + } + + ~File() { + fclose(_file); + } + +private: + FILE *_file; +}; +Дело в том, что если мы в конструкторе вызываем другой конструктор, то после его выполнения объект считается созданным и уничтожится в нужное время. diff --git "a/Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" "b/Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" new file mode 100644 index 0000000..c1ea182 --- /dev/null +++ "b/Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" @@ -0,0 +1,42 @@ +Тема: исключения, RAII, exception safety + +Источники: +1) светлая голова +2) "Кое-что об исключениях" https://habrahabr.ru/sandbox/28877/ +3) Лекции по с++ +4)"Как правильно использовать исключения ?" https://habrahabr.ru/post/263685/ +5)"Исключение и наследование" https://it.wikireading.ru/36139 +6) C++ для начинающих Липпман Стенли +7) cppreference +8) Основы RAII https://habrahabr.ru/sandbox/21603/ +9) RAII и необрабатываемые исключения https://habrahabr.ru/post/253749/ +10) +План: + +1) исключения + +1.1) Механизм работы (без реализации) +1.2) Как им пользоваться + +2) exception safety + +2.1) Мотивация +2.2) Суть +2.3) Особенности + +3) RAII +3.1) Основы +3.2) Пример с исключением +3.3) Пример с делегирующим конструктором. +Мотивацией является предыдущие пункты. + + +О чем я не сказал: +1) dynamic exception specification //DONE +2) exception_ptr +3) Nested exceptions +4) noexcept + +Что я не читал: +1)Подробная статья на хабре про исключения https://habrahabr.ru/post/208006/ +2) Что-то сложное про ликовку персональность и landing pad. From f1021bf0b477f1f41acbbc7aadce2f91c1815cb1 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Sat, 11 Nov 2017 04:01:06 +0300 Subject: [PATCH 19/30] some stuff --- Exceptions/Exception | 189 ------------------ Exceptions/Exception safety and RAII | 183 ----------------- ...6\320\277\321\200\320\276\321\201\321\213" | 35 ---- 3 files changed, 407 deletions(-) delete mode 100644 Exceptions/Exception delete mode 100644 Exceptions/Exception safety and RAII delete mode 100644 "Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" diff --git a/Exceptions/Exception b/Exceptions/Exception deleted file mode 100644 index 065c23b..0000000 --- a/Exceptions/Exception +++ /dev/null @@ -1,189 +0,0 @@ -План: -1) основы механизма -2) тонкости использования -3) Практика: Зачем это все собственно нужно? ---------------------------------------------------------------------------------------- -1) основы механизма - -В ходе работы программы часто могут возникнуть ситуации, которые не вписываются в общий алгоритм, или которые просто не отражают саму логику программы, но их тоже приходиться учитывать. Это, например, нехватка памяти, неправильный вывод пользователя, ошибка работы с ресурсами и т. д. С другой стороны, четко определить, что стоит отделять от основной логики сложно. Поэтому дадим пока довольно общие определения, а как этим пользоваться на практике мы обсудим отдельно. - -Исключение - это ситуация, которую МЫ сочли исключительной. Эти ситуации мы будет обрабатывать как-то особенно(отдельно от основной логики). Теперь нам нужно научиться выявлять, отслеживать и обрабатывать их. - -Пусть мы пишем функцию, которая принимает три числа кладет в первое результат деления второго на третие. -void div(int &res, int a, int b) { - res = a / b; -} - -Пусть исключительно ситуация возникает тогда, когда b = 0. И мы хотим обработать это исключение так: положить в res просто значение a. - -void div(int &res, int a, int b) { - try { - if (b == 0) - throw b; // генерируем исключение - // если исключения нет, то код работает в обычном режиме - res = a / b; - } - catch(int a) { // ловим исключение - res = a; // обрабатываем исключение - } -} -NB) типом исключения называется тип объекта, которым передаем в throw. Тип исключения должен ясно описывать ситуацию, так как по типу выбирается обработчик. - -Теперь рассмотрим используемы здесь конструкции: - -try{} -- защищенный блок. Здесь мы пишем код, в которым будут ловиться исключения. -catch(){} -- блок перехвата исключений. Здесь буду ловиться исключения указанные в (), и обрабатывать инструкциями в {} -throw -- оператор инициализации исключения. Он генерирует исключение. (Обычно говорят, "бросает" или "выбрасывает" исключение) - -блок try-catch - реализует обработку исключений. И имеет общий вид: -try {операторы защищенного блока} -// catch-блоки -catch() {обработчик} -... -catch() {обработчик} - -Давайте еще раз как, это работает: если нет исключения, то все работает как обычно и catch-блоки не выполняются. Если происходит выброс исключения. Создается объект типа int со значением b, потом происходит выбор нужного обработчика, по типу этого объекта. Когда он нашелся, то выполняется соответствующий код обработки. - -Теперь более подробно рассмотрим этот механизм: - -общий вид catch-блока: -1) catch(Type) {обработчик исключения } // мы не используем генерируемый исключением объект. -2) catch(Type exception_variable) { обработчик исключения } -3) catch(...) {обработчик исключения} // мы ловим исключение с любым типом объекта - -Что происходит когда мы генерируем исключение: -1) Создается статическая переменная со значением, заданным в операторе я throw. Она будет существовать до тех пор, пока исключение не будет обработано. Если переменная является объектом класса, то при создании вызовется конструктор копирования. -2) Прерывается выполнение защищенного try-блока(вызываются деструкторы временных объектов). -3) Выполняется поиск первого подходящего catch-блока - -Как происходит поиск catch-блока: -Блоки рассматриваются в том порядке, в котором они указанны. По следующем критериям: -1) если тип, указанный в catch-блоке, совпадает с типом исключения или является ссылкой на этот тип. -2) класс, заданный в catch-блоке, является предком класса, заданного в throw, и наследование открытое (public). -3) указатель, заданый в операторе throw, может быть преобразован по стандартным правилам к указателю, заданному в catch-блоке. -4) в catch-блоке указанно многоточие - -Если найдет нужный блок обработки, то выполняется его код, а остальные catch-блоки игнорируются. - -NB) Так как поиск ведется последовательно, то нужно учитывать порядок catch-блоков (Например, catch(...) должен быть последним). - -Если ни один catch-блок не подошел, то исключение не обработано, и поиск продолжается во внешнем коде (внешний блок или вызывающая функция). - -Если пройдя по цепочке вызовов функции мы не нашли ни одного подходящего обработчика, то вызывается функция terminate(), которая вызывает abort(). -NB) таком случае единственное, что мы модем сделать это написать свою функцию terminate() и зарегистрировать ее с помощью set_terminate. -Пример: -void my_terminate() { cout << "It's not a bug, it's a feature!"} -set_terminate(my_teminate); - -Так же мы можем после обработки исключение в конце catch-блока написать оператор throw без параметров, тогда исключение считается обработанным не до конца. И происходит повторный выброс исключения: по статическому объекту исключения ищется еще один обработчик, который лежит выше по цепочке вызовов. -NB) при это следует заметить, что при повторном выбросе исключения рассматривается не параметр текущего catch-блока, а именно изначальный статический объект. Именно он скопируется в качестве параметра в следующий обработчик. Поэтому его этот статический объект живет пока его исключение не обработается полностью. -NB NB) Поэтому при приведении тип исключения не теряется. - -Пусть мы бросили исключение Exception_heir, которое наследуется от Exception_base. -Следует различать catch(Exception_base exc) и сatch(Exception_base &exc) -В первом случае exc -- это копия базовой части статического объекта типа Exception_heir. -А во втором сработает динамическое связывание, и мы может использовать виртуальные функции, чего не произойдет в первом случае, не смотря на идентичность статических типов exc. - -Еще одним интересным моментом являются try-блоки в конструкторе. Конструктор - это очень важная часть класса, поэтому возможно ситуация, когда мы хотим обработать все исключения в нем. Наверно мы напишем так: -class St { -public: - St(): member() { - try { - // Constructor's code - } - {catchs} - } -private: - Member_type member; -} -Но заметим, что инициализация мемберов не находится внутри try-блока и не исключения возникшие в их конструкторах не поймаются. На самом деле тут есть особый синтаксис: -class St { -public: - St() try: member() { - { - // Constructor's code - } - {catchs} - } -private: - Member_type member; -} - -NB) Это называется функциональные try-блоки, то есть те, которые оборачивают все тело функции. Они есть и для обычных функций. -int main() try { - //some code -} -{catchs} -///////////////////NOTE/////////////////////////////// -У меня здесь почему-то не заработало, вот код: -#include - -class St { -public: - St(int a, int b, int c) try: a(a), b(b), c(c) { - throw 1; - } - catch(...) { - std::cout << "error!"; - } -private: - int a, b, c; -}; - -int main () { - St a(1, 2, 3); - return 0; -} -выводится строка "error!", но после этого дамп по памяти. -///////////////////NOTE/////////////////////////////// - -Также важно помнить, что если в конструкторе происходит исключение, то для него не вызовется деструктор, так объект еще не считается созданным. - -NB) При генерации исключения параметр throw копируется в статическую переменную того же типа, что и СТАТИЧЕСКИЙ тип параметр. То есть такой код работать не бдует: - -try { - Exception_base *exc = new Exception_heir(); - throw exc; -} catch (Exception_heir *exc) { - //some code -} - -2) Best practice - -Теперь, когда мы разобрались как работает механизм исключений, не плохо понять когда и где его лучше использовать. - -Я опишу несколько ситуаций, которые покажут плохие и хорошие стороны исключений, и постараюсь подвести итог в конце. - -Минусы: -1) Иногда бывает сложно понять, где происходит исключение и как его следует обработать. Эти ситуации возникают, когда нам приходится обрабатывать исключения, приходящие нам от внешнего кода. - - Во-первых без документации невозможно понять какие функции бросают исключения и какого типа. - - Во-вторых когда, мы узнаем тип исключения и какая функция его бросает, не всегда по типу исключения очевидно, как его следует обработать. - - real story: Бывает, что вы используете библиотеку с закрытым исходным кодом. И неожиданно одна из функции библиотеки кидает нам исключения типа int. И как его следует обработать ? :( - -2) Не всегда мы знаем, как нужно обрабатывать ошибки, поэтому иногда больший смысл имеет просто логировать ошибки и просто продолжать исполнение кода, игнорируя ошибку. Это можно сделать catch(...), но если в нашем коде это распространенная ситуация, то механизм исключений избыточен. - -Плюсы: -Исключения нужны там, где мы хотим, чтобы программа всегда работала корректно. -1) Например, мы увеличиваем вектор, и если у нас кончилась память для resize, то программа не падает и не убивает те данные, которые уже лежат в векторе. -2) Это механизм очень удобный, когда мы организовываем исключения в иерархии классов. Создав такую структуры мы при обработке, может обрабатывать как более общие ошибки, так и более специализированные. -Пример: -class StackException {}; -class popOnEmpty(): public StackException {}; -class pushOnFull(): public StackException {}; - -Причем сгенерировав исключение типа popOnEmpty() -Мы можем в разных обработчиках независимо выбирать: обработать как popOnEmpty или как StackException, так как тип исключения не теряется при повторной генерации этого исключения. - -3)Полезно знать про стандартные исключения, которые также связанны наследованием. -|) std::ecxeption - | | - v v -||) std::bad_alloc, std::bad_cast, std::bad_typeid и т. д. - -Подробнее можно почитать здесь: https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm -http://en.cppreference.com/w/cpp/error/exception - -4) Хорошим тоном является наследование от std::exception diff --git a/Exceptions/Exception safety and RAII b/Exceptions/Exception safety and RAII deleted file mode 100644 index 0552621..0000000 --- a/Exceptions/Exception safety and RAII +++ /dev/null @@ -1,183 +0,0 @@ -Exception safety - -Механизм исключений является довольно мощным инструментом. Но при этом не редко возникает проблемы с его читаемостью. Далеко не всегда очевидно откуда пришло исключение и как стоит его обрабатывать. Некоторые сравнивают эффект вызова исключений с эффектами от вызова goto. - -Поэтому относительно работы исключений с объектами существуют Гарантии безопасности исключений (Exception safety). Это ряд уровней безопасности, которые присваиваются методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. - -Уровни гарантий: - -1) "No guarantees" - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что объект больше не будет использоваться. -2) "Basic guarantees" - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. -3) "Strong guarantees" - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. -3) "Nothrow guarantees" - Кроме базовой гарантии, гарантируется, что исключения не возникают. - - -Теперь давайте более подробно: - -Гарантия nothrow: - -Она есть у очень небольшого количества функции: swap, vector::pop_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. - -Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. - -NB) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. -Подробнее в статье про перемещение. - -Примеры нарушения базовой гарантии: -Главным способом предотвращением утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). - - Offtops: - -1) Можно попросить оператор new не кидать исключение с помощью константы std::nothrow - -2) Можно указать какие исключение может кидать функция: -void f() throw(int, double)* -*что здесь можно написать: -1) throw() - функция не кидает исключения -2) throw(T1, T2, T3) - функция может кидать исключения T1, T2, T3 -3) throw(...) - функция может кидать любые исключения -Это не используется в c++11, так как возникли какие-то проблемы, и по факту полезным оказался только throw(). - -RAII-class: -Основы: -"Resource Acquision is Initialozation" или "Захват ресурса - это инициализация"- это идиома класса, который инкапсулирует управление каким-то ресурсом. Она значит, что объект этого класса, получает доступ к ресурсу и удерживает его в течении своей жизни, а потом этот ресурс высвобождается. -В конструкторе он должен захватить ресурс(открыть файл, выделить файл и т. д.), а в десткрукторе освободить его(закрыть файл, освободить память и т. д.). - -Также важно подумать, что должно происходить при копировании объекта, часто мы просто явно запрещаем это делать. - -Что это дает? -1) Удобство кода: нам не приходится каждый раз в конце тела функции освобождать ресурсы. Мы просто заводим локальную переменную. И когда выполнение текущего блока будет завершено, локальные объекты RAII-классов удалятся и ресурсы освободятся автоматически. -2) Безопасность исключений: Если вызывается исключение, то нам гарантируется, что стек очиститься и все локальные объекты удаляться, а значит и освободятся ресурсы. Без RAII приходится в ручную освобождать все ресурсы. Причем нужно учитывать какие ресурсы успели захватить до исключения, а какие нет. -3) Часто важно освобождать ресурсы в обратном порядке, относительно того, как они были захвачены. Как раз раскрутка стека при удалении локальных объектов, это поддерживает. - -NB) Это идиома работает не только в С++, а любом языке с предсказуемым временем жизни объектов. - -Вот не большой пример RAII-класса, который будет управлять файлом. -Ресурсом является данные типа FILE (формат файла в Си). - -class File { -public: - File(char const *filename, char const *mode) - : _file(fopen(filename, mode)) { } //захват ресурса - - ~File() { - fclose(_file); // освобождение ресурса - } - - File(File const &) = delete; - File operator=(File const &) = delete; - -private: - FILE *_file; -} - -Тонкие моменты в RAII: - -1) Необрабатываемые исключения -Вот код: - -int main { - File("input.txt", "r"); - // здесь происходит исключение - return 0; -} - -Что не так? -Проблема в том, что не смотря на то, что мы использовали RAII-класс, ресурс не будет освобожден, так как удаление локальных файлов, гарантируется, только при перехвате исключения, иначе поведение зависит от реализации. -Поэтому main следует написать так: -int main() { - try { - File("input.txt", "r"); - // здесь происходит исключение - } - catch(...) { - cerr << "Unexpected exception.\n"; - } -} -Теперь все ок. - -2) Исключение в деструкторе. Но это проблема сама по себе. - -3) Исключения в конструкторах. - -Вернемся к предыдущему примеру File. -Пусть в конструкторе есть код, который может сгенерить исключение, тогда возникает проблема освобождения ресурса. -File::File(char const *filename, char const *mode) - : _file(fopen(filename, mode)) { - //код допускающий исключение - } -Если в теле конструктора происходит исключение, то деструктор не вызовется, так как объект не считается созданным. Что делать? - -Решение № 1 -Написать try-catch -class File { -public: - File(char const *filename, char const *mode) - try - : _file(fopen(filename, mode)) { - //код допускающий исключение - } - catch (...) { - destruct_obj(); - } - - ~File() { - destruct_obj(); - } - -private: - void destruct_obj() { - fclose(_file); - } - - FILE * _file; -}; -Минусы решения: можно лучше. -Решение № 2 -Можно сделать отдельный подкласс, который хранит в себе ресурс. -class File { - struct FileHandle { - FileHandle(FILE *fh) - : _fh(fh) { } - - ~FileHandle() { - fclose(_fh); - } - - FILE *_fh; - } - -public: - File(char const * filename, char const * mode) - : _file(fopen(filename, mode)) { - // код допускающий исключения - } - - ~File() = default; - -private: - FileHandle _file; -}; -Теперь все тоже ок, так как при возникновении исключения, вызовутся деструкторы от все мемберов. -Минусы решения: можно еще лучше -Решение № 3 (Изящное) -Делегирующий конструктор. -class File -{ - File(FILE * file) - : _file(file) { } - -public: - File(char const * filename, char const * mode) - : File(fopen(filename, mode)) { - // код допускающий исключения - } - - ~File() { - fclose(_file); - } - -private: - FILE *_file; -}; -Дело в том, что если мы в конструкторе вызываем другой конструктор, то после его выполнения объект считается созданным и уничтожится в нужное время. diff --git "a/Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" "b/Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" deleted file mode 100644 index 6256f87..0000000 --- "a/Exceptions/\320\222\320\276\320\277\321\200\320\276\321\201\321\213" +++ /dev/null @@ -1,35 +0,0 @@ -On 11/02/2017 11:40 PM, Павел Головин wrote: -> Здравствуйте, я начал писать конспект и у меня возник вопрос по поводу -> Excetions: -> -> Нужно ли писать про реализацию исключений, там про всякие прерывания -> и т. д. ? - -Я считаю, что нет. Сама по себе тема эта интересная, но достаточно -объемная. Ты сам видишь размер статьи про SEH. И рассказ про то как -работают исключения в Линуксе такой же объемный будет. - -Я думаю, если бы у нас уже был готовый конспект, то это была бы хорошая -advanced-тема. - -> Вопрос возник из-за того, что я нашел на сайте лекций вот эту ссылку: -> http://sorokin.github.io/cpp-course/seh/seh.html -> -> План пока такой: -> -> 1) описать сам механизм, типа: напишем так будет то-то. -> 2) Постараться объяснить где его следует использовать. - -Да, согласен. В принципе, я когда рассказываю его на лекции я -рассказываю по такому же плану. - -1) Существует try, существует catch, работают так. -2) И дальше пошли разнообразные best practices. - -Я правда часто начинаю рассказ с того, что было до этого (а ля -error-codes, goto cleanup, etc) и их недостатки, но в конспекте это пока -не нужно, а если и нужно то лучше наверное отдельной главой. - -10.11.2017 Вопросы: -1) Что произойдет если во время исключения броситься исключение? -2) Есть ли рекомендации к гарантиям конструкторов? From 6fce8383d5434223ad504595fbf396b2d6fc0b29 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Sat, 11 Nov 2017 18:15:34 +0300 Subject: [PATCH 20/30] stuff --- ...5\321\201\320\277\320\265\320\272\321\202" | 374 ------------------ ...1\320\277\320\265\320\272\321\202\320\260" | 42 -- 2 files changed, 416 deletions(-) delete mode 100644 "Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" delete mode 100644 "Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" diff --git "a/Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" "b/Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" deleted file mode 100644 index 69ca96c..0000000 --- "a/Exceptions/\320\232\320\276\320\275\321\201\320\277\320\265\320\272\321\202" +++ /dev/null @@ -1,374 +0,0 @@ -План: -1) основы механизма -2) тонкости использования -3) Практика: Зачем это все собственно нужно? ---------------------------------------------------------------------------------------- -1) основы механизма - -В ходе работы программы часто могут возникнуть ситуации, которые не вписываются в общий алгоритм, или которые просто не отражают саму логику программы, но их тоже приходиться учитывать. Это, например, нехватка памяти, неправильный вывод пользователя, ошибка работы с ресурсами и т. д. С другой стороны, четко определить, что стоит отделять от основной логики сложно. Поэтому дадим пока довольно общие определения, а как этим пользоваться на практике мы обсудим отдельно. - -Исключение - это ситуация, которую МЫ сочли исключительной. Эти ситуации мы будет обрабатывать как-то особенно(отдельно от основной логики). Теперь нам нужно научиться выявлять, отслеживать и обрабатывать их. - -Пусть мы пишем функцию, которая принимает три числа кладет в первое результат деления второго на третие. -void div(int &res, int a, int b) { - res = a / b; -} - -Пусть исключительно ситуация возникает тогда, когда b = 0. И мы хотим обработать это исключение так: положить в res просто значение a. - -void div(int &res, int a, int b) { - try { - if (b == 0) - throw b; // генерируем исключение - // если исключения нет, то код работает в обычном режиме - res = a / b; - } - catch(int a) { // ловим исключение - res = a; // обрабатываем исключение - } -} -NB) типом исключения называется тип объекта, которым передаем в throw. Тип исключения должен ясно описывать ситуацию, так как по типу выбирается обработчик. - -Теперь рассмотрим используемы здесь конструкции: - -try{} -- защищенный блок. Здесь мы пишем код, в которым будут ловиться исключения. -catch(){} -- блок перехвата исключений. Здесь буду ловиться исключения указанные в (), и обрабатывать инструкциями в {} -throw -- оператор инициализации исключения. Он генерирует исключение. (Обычно говорят, "бросает" или "выбрасывает" исключение) - -блок try-catch - реализует обработку исключений. И имеет общий вид: -try {операторы защищенного блока} -// catch-блоки -catch() {обработчик} -... -catch() {обработчик} - -Давайте еще раз как, это работает: если нет исключения, то все работает как обычно и catch-блоки не выполняются. Если происходит выброс исключения. Создается объект типа int со значением b, потом происходит выбор нужного обработчика, по типу этого объекта. Когда он нашелся, то выполняется соответствующий код обработки. - -Теперь более подробно рассмотрим этот механизм: - -общий вид catch-блока: -1) catch(Type) {обработчик исключения } // мы не используем генерируемый исключением объект. -2) catch(Type exception_variable) { обработчик исключения } -3) catch(...) {обработчик исключения} // мы ловим исключение с любым типом объекта - -Что происходит когда мы генерируем исключение: -1) Создается статическая переменная со значением, заданным в операторе я throw. Она будет существовать до тех пор, пока исключение не будет обработано. Если переменная является объектом класса, то при создании вызовется конструктор копирования. -2) Прерывается выполнение защищенного try-блока(вызываются деструкторы временных объектов). -3) Выполняется поиск первого подходящего catch-блока - -Как происходит поиск catch-блока: -Блоки рассматриваются в том порядке, в котором они указанны. По следующем критериям: -1) если тип, указанный в catch-блоке, совпадает с типом исключения или является ссылкой на этот тип. -2) класс, заданный в catch-блоке, является предком класса, заданного в throw, и наследование открытое (public). -3) указатель, заданый в операторе throw, может быть преобразован по стандартным правилам к указателю, заданному в catch-блоке. -4) в catch-блоке указанно многоточие - -Если найдет нужный блок обработки, то выполняется его код, а остальные catch-блоки игнорируются. - -NB) Так как поиск ведется последовательно, то нужно учитывать порядок catch-блоков (Например, catch(...) должен быть последним). - -Если ни один catch-блок не подошел, то исключение не обработано, и поиск продолжается во внешнем коде (внешний блок или вызывающая функция). - -Если пройдя по цепочке вызовов функции мы не нашли ни одного подходящего обработчика, то вызывается функция terminate(), которая вызывает abort(). -NB) таком случае единственное, что мы модем сделать это написать свою функцию terminate() и зарегистрировать ее с помощью set_terminate. -Пример: -void my_terminate() { cout << "It's not a bug, it's a feature!"} -set_terminate(my_teminate); - -Так же мы можем после обработки исключение в конце catch-блока написать оператор throw без параметров, тогда исключение считается обработанным не до конца. И происходит повторный выброс исключения: по статическому объекту исключения ищется еще один обработчик, который лежит выше по цепочке вызовов. -NB) при это следует заметить, что при повторном выбросе исключения рассматривается не параметр текущего catch-блока, а именно изначальный статический объект. Именно он скопируется в качестве параметра в следующий обработчик. Поэтому его этот статический объект живет пока его исключение не обработается полностью. -NB NB) Поэтому при приведении тип исключения не теряется. - -Пусть мы бросили исключение Exception_heir, которое наследуется от Exception_base. -Следует различать catch(Exception_base exc) и сatch(Exception_base &exc) -В первом случае exc -- это копия базовой части статического объекта типа Exception_heir. -А во втором сработает динамическое связывание, и мы может использовать виртуальные функции, чего не произойдет в первом случае, не смотря на идентичность статических типов exc. - -Еще одним интересным моментом являются try-блоки в конструкторе. Конструктор - это очень важная часть класса, поэтому возможно ситуация, когда мы хотим обработать все исключения в нем. Наверно мы напишем так: -class St { -public: - St(): member() { - try { - // Constructor's code - } - {catchs} - } -private: - Member_type member; -} -Но заметим, что инициализация мемберов не находится внутри try-блока и не исключения возникшие в их конструкторах не поймаются. На самом деле тут есть особый синтаксис: -class St { -public: - St() try: member() { - { - // Constructor's code - } - {catchs} - } -private: - Member_type member; -} - -NB) Это называется функциональные try-блоки, то есть те, которые оборачивают все тело функции. Они есть и для обычных функций. -int main() try { - //some code -} -{catchs} -///////////////////NOTE/////////////////////////////// -У меня здесь почему-то не заработало, вот код: -#include - -class St { -public: - St(int a, int b, int c) try: a(a), b(b), c(c) { - throw 1; - } - catch(...) { - std::cout << "error!"; - } -private: - int a, b, c; -}; - -int main () { - St a(1, 2, 3); - return 0; -} -выводится строка "error!", но после этого дамп по памяти. -///////////////////NOTE/////////////////////////////// - -Также важно помнить, что если в конструкторе происходит исключение, то для него не вызовется деструктор, так объект еще не считается созданным. - -NB) При генерации исключения параметр throw копируется в статическую переменную того же типа, что и СТАТИЧЕСКИЙ тип параметр. То есть такой код работать не бдует: - -try { - Exception_base *exc = new Exception_heir(); - throw exc; -} catch (Exception_heir *exc) { - //some code -} - -2) Best practice - -Теперь, когда мы разобрались как работает механизм исключений, не плохо понять когда и где его лучше использовать. - -Я опишу несколько ситуаций, которые покажут плохие и хорошие стороны исключений, и постараюсь подвести итог в конце. - -Минусы: -1) Иногда бывает сложно понять, где происходит исключение и как его следует обработать. Эти ситуации возникают, когда нам приходится обрабатывать исключения, приходящие нам от внешнего кода. - - Во-первых без документации невозможно понять какие функции бросают исключения и какого типа. - - Во-вторых когда, мы узнаем тип исключения и какая функция его бросает, не всегда по типу исключения очевидно, как его следует обработать. - - real story: Бывает, что вы используете библиотеку с закрытым исходным кодом. И неожиданно одна из функции библиотеки кидает нам исключения типа int. И как его следует обработать ? :( - -2) Не всегда мы знаем, как нужно обрабатывать ошибки, поэтому иногда больший смысл имеет просто логировать ошибки и просто продолжать исполнение кода, игнорируя ошибку. Это можно сделать catch(...), но если в нашем коде это распространенная ситуация, то механизм исключений избыточен. - -Плюсы: -Исключения нужны там, где мы хотим, чтобы программа всегда работала корректно. -1) Например, мы увеличиваем вектор, и если у нас кончилась память для resize, то программа не падает и не убивает те данные, которые уже лежат в векторе. -2) Это механизм очень удобный, когда мы организовываем исключения в иерархии классов. Создав такую структуры мы при обработке, может обрабатывать как более общие ошибки, так и более специализированные. -Пример: -class StackException {}; -class popOnEmpty(): public StackException {}; -class pushOnFull(): public StackException {}; - -Причем сгенерировав исключение типа popOnEmpty() -Мы можем в разных обработчиках независимо выбирать: обработать как popOnEmpty или как StackException, так как тип исключения не теряется при повторной генерации этого исключения. - -3)Полезно знать про стандартные исключения, которые также связанны наследованием. -|) std::ecxeption - | | - v v -||) std::bad_alloc, std::bad_cast, std::bad_typeid и т. д. - -Подробнее можно почитать здесь: https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm -http://en.cppreference.com/w/cpp/error/exception - -4) Хорошим тоном является наследование от std::exception - - -Exception safety - -Механизм исключений является довольно мощным инструментом. Но при этом не редко возникает проблемы с его читаемостью. Далеко не всегда очевидно откуда пришло исключение и как стоит его обрабатывать. Некоторые сравнивают эффект вызова исключений с эффектами от вызова goto. - -Поэтому относительно работы исключений с объектами существуют Гарантии безопасности исключений (Exception safety). Это ряд уровней безопасности, которые присваиваются методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. - -Уровни гарантий: - -1) "No guarantees" - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что объект больше не будет использоваться. -2) "Basic guarantees" - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. -3) "Strong guarantees" - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. -3) "Nothrow guarantees" - Кроме базовой гарантии, гарантируется, что исключения не возникают. - - -Теперь давайте более подробно: - -Гарантия nothrow: - -Она есть у очень небольшого количества функции: swap, vector::pop_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. - -Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. - -NB) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. -Подробнее в статье про перемещение. - -Примеры нарушения базовой гарантии: -Главным способом предотвращением утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). - - Offtops: - -1) Можно попросить оператор new не кидать исключение с помощью константы std::nothrow - -2) Можно указать какие исключение может кидать функция: -void f() throw(int, double)* -*что здесь можно написать: -1) throw() - функция не кидает исключения -2) throw(T1, T2, T3) - функция может кидать исключения T1, T2, T3 -3) throw(...) - функция может кидать любые исключения -Это не используется в c++11, так как возникли какие-то проблемы, и по факту полезным оказался только throw(). - -RAII-class: -Основы: -"Resource Acquision is Initialozation" или "Захват ресурса - это инициализация"- это идиома класса, который инкапсулирует управление каким-то ресурсом. Она значит, что объект этого класса, получает доступ к ресурсу и удерживает его в течении своей жизни, а потом этот ресурс высвобождается. -В конструкторе он должен захватить ресурс(открыть файл, выделить файл и т. д.), а в десткрукторе освободить его(закрыть файл, освободить память и т. д.). - -Также важно подумать, что должно происходить при копировании объекта, часто мы просто явно запрещаем это делать. - -Что это дает? -1) Удобство кода: нам не приходится каждый раз в конце тела функции освобождать ресурсы. Мы просто заводим локальную переменную. И когда выполнение текущего блока будет завершено, локальные объекты RAII-классов удалятся и ресурсы освободятся автоматически. -2) Безопасность исключений: Если вызывается исключение, то нам гарантируется, что стек очиститься и все локальные объекты удаляться, а значит и освободятся ресурсы. Без RAII приходится в ручную освобождать все ресурсы. Причем нужно учитывать какие ресурсы успели захватить до исключения, а какие нет. -3) Часто важно освобождать ресурсы в обратном порядке, относительно того, как они были захвачены. Как раз раскрутка стека при удалении локальных объектов, это поддерживает. - -NB) Это идиома работает не только в С++, а любом языке с предсказуемым временем жизни объектов. - -Вот не большой пример RAII-класса, который будет управлять файлом. -Ресурсом является данные типа FILE (формат файла в Си). - -class File { -public: - File(char const *filename, char const *mode) - : _file(fopen(filename, mode)) { } //захват ресурса - - ~File() { - fclose(_file); // освобождение ресурса - } - - File(File const &) = delete; - File operator=(File const &) = delete; - -private: - FILE *_file; -} - -Тонкие моменты в RAII: - -1) Необрабатываемые исключения -Вот код: - -int main { - File("input.txt", "r"); - // здесь происходит исключение - return 0; -} - -Что не так? -Проблема в том, что не смотря на то, что мы использовали RAII-класс, ресурс не будет освобожден, так как удаление локальных файлов, гарантируется, только при перехвате исключения, иначе поведение зависит от реализации. -Поэтому main следует написать так: -int main() { - try { - File("input.txt", "r"); - // здесь происходит исключение - } - catch(...) { - cerr << "Unexpected exception.\n"; - } -} -Теперь все ок. - -2) Исключение в деструкторе. Но это проблема сама по себе. - -3) Исключения в конструкторах. - -Вернемся к предыдущему примеру File. -Пусть в конструкторе есть код, который может сгенерить исключение, тогда возникает проблема освобождения ресурса. -File::File(char const *filename, char const *mode) - : _file(fopen(filename, mode)) { - //код допускающий исключение - } -Если в теле конструктора происходит исключение, то деструктор не вызовется, так как объект не считается созданным. Что делать? - -Решение № 1 -Написать try-catch -class File { -public: - File(char const *filename, char const *mode) - try - : _file(fopen(filename, mode)) { - //код допускающий исключение - } - catch (...) { - destruct_obj(); - } - - ~File() { - destruct_obj(); - } - -private: - void destruct_obj() { - fclose(_file); - } - - FILE * _file; -}; -Минусы решения: можно лучше. -Решение № 2 -Можно сделать отдельный подкласс, который хранит в себе ресурс. -class File { - struct FileHandle { - FileHandle(FILE *fh) - : _fh(fh) { } - - ~FileHandle() { - fclose(_fh); - } - - FILE *_fh; - } - -public: - File(char const * filename, char const * mode) - : _file(fopen(filename, mode)) { - // код допускающий исключения - } - - ~File() = default; - -private: - FileHandle _file; -}; -Теперь все тоже ок, так как при возникновении исключения, вызовутся деструкторы от все мемберов. -Минусы решения: можно еще лучше -Решение № 3 (Изящное) -Делегирующий конструктор. -class File -{ - File(FILE * file) - : _file(file) { } - -public: - File(char const * filename, char const * mode) - : File(fopen(filename, mode)) { - // код допускающий исключения - } - - ~File() { - fclose(_file); - } - -private: - FILE *_file; -}; -Дело в том, что если мы в конструкторе вызываем другой конструктор, то после его выполнения объект считается созданным и уничтожится в нужное время. diff --git "a/Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" "b/Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" deleted file mode 100644 index c1ea182..0000000 --- "a/Exceptions/\320\237\320\273\320\260\320\275 \320\272\320\276\320\275\321\201\320\277\320\265\320\272\321\202\320\260" +++ /dev/null @@ -1,42 +0,0 @@ -Тема: исключения, RAII, exception safety - -Источники: -1) светлая голова -2) "Кое-что об исключениях" https://habrahabr.ru/sandbox/28877/ -3) Лекции по с++ -4)"Как правильно использовать исключения ?" https://habrahabr.ru/post/263685/ -5)"Исключение и наследование" https://it.wikireading.ru/36139 -6) C++ для начинающих Липпман Стенли -7) cppreference -8) Основы RAII https://habrahabr.ru/sandbox/21603/ -9) RAII и необрабатываемые исключения https://habrahabr.ru/post/253749/ -10) -План: - -1) исключения - -1.1) Механизм работы (без реализации) -1.2) Как им пользоваться - -2) exception safety - -2.1) Мотивация -2.2) Суть -2.3) Особенности - -3) RAII -3.1) Основы -3.2) Пример с исключением -3.3) Пример с делегирующим конструктором. -Мотивацией является предыдущие пункты. - - -О чем я не сказал: -1) dynamic exception specification //DONE -2) exception_ptr -3) Nested exceptions -4) noexcept - -Что я не читал: -1)Подробная статья на хабре про исключения https://habrahabr.ru/post/208006/ -2) Что-то сложное про ликовку персональность и landing pad. From d905bf041376afd0c3d955caa09f714efed27d62 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Fri, 22 Dec 2017 02:13:55 +0300 Subject: [PATCH 21/30] add Exceptions(Golovin) and Perfect Forwarding(Kokorin) --- Exception_and_RAII/Exception-safety.tex | 64 +++ Exception_and_RAII/Exception.tex | 346 ++++++++++++++++ Exception_and_RAII/RAII.tex | 197 +++++++++ Perfect_forwarding.tex | 507 ++++++++++++++++++++++++ main.tex | 5 +- 5 files changed, 1118 insertions(+), 1 deletion(-) create mode 100644 Exception_and_RAII/Exception-safety.tex create mode 100644 Exception_and_RAII/Exception.tex create mode 100644 Exception_and_RAII/RAII.tex create mode 100644 Perfect_forwarding.tex diff --git a/Exception_and_RAII/Exception-safety.tex b/Exception_and_RAII/Exception-safety.tex new file mode 100644 index 0000000..6585c09 --- /dev/null +++ b/Exception_and_RAII/Exception-safety.tex @@ -0,0 +1,64 @@ +\section{Exception safety} +\subsection{Мотивирующий пример} +Пусть у нас есть функция \mintinline{c++}{vector::resize()}; + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void vector::resize(size_t size) { + if (size > curr_size) { + cnt_add = size - curr_size; // количество элементов для добавления. + for (size_t i = 0; i < cnt_add; ++i) { + push_back(T()); + } + } +} +\end{minted} + + +Это код не использует исключения, то есть не вызывает их и не обрабатывает. Но если будет ошибка выделения памяти при расширении вектора, то исключение возникнет в функции push\_back(), потом пробросятся через resize() наружу. + +То есть проблема в том, что мы не используя механизм исключений все равно можем получить от него проблемы. Как например, не пойманное исключение. + +\subsection{Определение} + +Поэтому существуют Гарантии безопасности исключений (Exception safety). Это некий контракт исключений, который представляет из себя ряд уровней безопасности, которые присваиваются всем методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. + +Уровни гарантий: +\begin{enumerate} +\item \textbf{<>} - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя. +\item \textbf{<>} - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. +\item \textbf{<>} - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. +\item \textbf{<>} - Кроме базовой гарантии, гарантируется, что исключения не возникают. +\end{enumerate} + +\subsection{Основные моменты} + +Теперь давайте рассмотрим важные моменты: +\begin{itemize} +\item Методы пользовательского интерфейса должны удовлетворять базовым или строгим гарантиям. Это избавляет пользователей от утечек памяти и инвалидных данных. + +\item Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. + +\item Важность гарантии nothrow: +Она есть у очень небольшого количества функции: swap, vector::pop\_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. +\end{itemize} + +\subsection{Best practice} + +Пример: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +vector& vector::operator=(vector const& other) { + return this->swap(vector(other)); // swap trick! +} +\end{minted} + +Мы копируем other во временный объект vector(other), а потом делаем swap с ним. Если произойдет исключение при копировании other во временный объект, то оно пробросится к нам. swap() не выполнится и исключение проброситься дальше. Наш объект не поменяется. + +\textcolor{red}{NB}) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. + +Главным способом предотвращением утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). + +\textcolor{red}{Offtops:} + +Можно попросить оператор \mintinline{c++}{new} не кидать исключение с помощью константы \mintinline{c++}{std::nothrow} diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex new file mode 100644 index 0000000..3e99756 --- /dev/null +++ b/Exception_and_RAII/Exception.tex @@ -0,0 +1,346 @@ +\section{Exceptions} +\subsection{Введение} +Часто нехватка динамической памяти, неправильный ввод пользователя, ошибка с файловой системой, приводят к тому, что продолжение исполнения логики программы не возможно. Например, если наша функция foo вызывает malloc и malloc вернул ошибку, то функция foo должна завершится и тоже вернуть ошибку. Возможно, что вызывающая сторона функции foo, тоже проверит возвращаемое значение функции foo и завершится с ошибкой. + +В C это поведение достигалось, явной проверкой возвращаемого значения функции с помощю if и return'а в случае ошибки. C++ имеет встроенный в язык механизм поддержки такого поведения. Он называется механизмом исключений. + +Рассмотрим следующую функцию деления: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void div(int a, int b) { + return a / b; +} +\end{minted} + +Если в эту функцию передать в качестве $b$ $0$, то произойдет undefined behavior. Предположим, что мы хотим, чтобы функция сообщала об ошибке, когда $b = 0$. Для этого сначала необходимо объявить класс исключения, объекты которого будут хранить в себе информацию об исключении. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class Division_by_zero() { + int dividend + string message; + Division_by_zero(int ÷nd, string const &message) : + dividend(dividend), message(message) { } +}; +\end{minted} + +Теперь можно переписать функцию $div$ следующим образом: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void div(int a, int b) { + if (b == 0) // возникает исключительное состояние + throw Division_by_zero(a, "in function div(int, int)"); // генерируем исключение. + return a / b; +} +\end{minted} + +Оператор throw завершает исполнение текущей функции и возвращает ошибку в вызывающую функцию. Вызывающая сторона может обрабатывать исключение следующим образом: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int main() { + int n; + cin >> n; + try { // здесь указываем опрераторы, в которых мы хотим ловить исключения. + for (int i = 0, a, b; i < n; ++i) { + cin >> a >> b; + cout << div(a, b); + } + } catch(Division_by_zero const& obj) { //здесь указываем тип исключения, которое мы хотим обработать + // здесь обрабатываем исключение + cout << obj.dividend << "div by 0 " << obj.message(); + } +} +\end{minted} + +В данном примере, при завершении div с исключением, цикл for прерывается и исполняется catch-блок, который выводит сообщение об ошибке. После чего функция main завершается. + +Если исключения не возникает, то цикл for отработает до конца, catch-блок вызван не будет. + +\subsection{Описание конструкций} +Рассмотрим используемые конструкции подробнее: + +\mintinline{c++}{try{}} -- защищенный блок. Здесь пишется код, исключения в котором необходимо ловить и обрабатывать. + +\mintinline{c++}{catch(){}} -- блок перехвата исключений или блок обработки или обработчик. Здесь будут ловиться исключения, тип которых совпадает по определенным правилам с типом указанным в (), и обрабатываться инструкциями в \{\} + +\mintinline{c++}{throw} -- оператор генерирует исключение. (Иногда говорят, "бросает"\ или "выбрасывает"\ исключение) + +Теперь давай рассмотрим детали работы этого механизма. + +Блок \mintinline{c++}{try-catch} используется для обработки исключений. И имеет общий вид: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +try { /*операторы защищенного блока*/ } +// catch-блоки +catch(exception1 const& e) {/*код обработки*/} +catch(exception2 const& e) {/*код обработки*/} +... +catch(exceptionN const& e) {/*код обработки*/} + +\end{minted} +Если изнутри блока try вылетит исключение то, компилятор попытается подобрать подходящий catch-блок. В catch блоке указывается какие действия необходимо сделать, при возникновении исключения указанного типа. + +Catch-блок может иметь две формы: +\begin{itemize} + \item + \mintinline{c++}{catch(/*declaration*/) { /*обработчик исключения*/ }} Ловит исключение указанного или производных от него типов. Переменной исключения можно дать имя. Это позволяет обращаться к объекту исключения. + \item + \mintinline{c++}{catch(...) { /*обработчик исключения*/ }} + Ловит исключения всех типов. В этом случае невозможно обращаться к объекту исключения. +\end{itemize} + +Что происходит, когда мы генерируем исключение: +\begin{enumerate} + \item + Создается копия объекта переданного в оператор throw. Этот объект будет существовать до тех пор, пока исключение не будет обработано. Если тип объекта имеет конструктор копирования, то он будет вызван. + \item + Прерывается исполнение программы. + \item + Выполняется раскрутка стека, пока исключение не будет обработано. +\end{enumerate} + +При раскрутке стека, вызываются деструкторы локальных переменных в обратном порядке их объявления. После разрушения всех локальных объектов текущей функции процесс продолжается в вызывающей функции. Раскрутка стека продолжается пока не будет найден try-catch-блок. При нахождении try-catch-блока, проверяется, может ли исключение быть обработано одним их catch-блоков. + +\subsection{Как ловится исключение?} + +Catch-блоки проверяются в том порядке, в котором написаны. Обработчик считается подходящим если: +\begin{enumerate} + \item + Тип, указанный в catch-блоке, совпадает с типом исключения или является ссылкой на этот тип. + \item + Класс, заданный в catch-блоке, является предком класса, заданного в throw, и наследование открытое (public). + \item + Указатель, заданный в операторе throw, может быть преобразован по стандартным правилам к указателю, заданному в catch-блоке. + \item + В catch-блоке указанно многоточие. +\end{enumerate} + +Если найдет нужный catch-блок, то выполняется его код, остальные catch-блоки игнорируются, а выполнение продолжается после try...catch-блока и исключение считается обработанным. Если ни один catch-блок не подошел, процесс раскрутки стека продолжается. + +\textcolor{red}{NB}) Так как поиск ведется последовательно, то нужно учитывать порядок catch-блоков (Например, catch(...) должен быть последним). + +\textcolor{red}{NB}) Также при наследовании классов исключений следует различать catch(type\& obj) и catch(type obj). В первом случае obj ссылается на этот объект и копии не создается. Во втором случае при входе в catch блок делается копия объекта-исключения, вследствии чего мы теряем возможность вызывать виртуальные функции. + +Пример: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +struct Exception_base { + virtual char const* msg() const { + return "base"; + } +}; + +struct Exception_derived : Exception_base { + virtual char const* msg() const { + return "derived"; + } +}; + +int f() { + try { + throw Exception_derived(); + } + catch (base e) { + std::cout << e.msg() << std::endl; + } +} + +int g() { + try { + throw Exception_derived(); + } + catch (base const& e) { + std::cout << e.msg() << std::endl; + } +} +\end{minted} + +В данном примере g() выводит <>, а функция f() выводит <>, поскольку объект исключения был скопирован с базы объекта, который мы передали в оператор throw и новая копия имеет тип base. + +В некоторых случаях внутри catch-блока может быть необходимо не завершать раскрутку стека. Для этого существует специальная форма оператора throw без аргумента. Она означает проброс текущего исключения с сохранением его типа. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + +struct Exception_base { + virtual char const* msg() const { + return "base"; + } +}; + +struct Exception_derived : Exception_base { + virtual char const* msg() const { + return "derived"; + } +}; + +void thrid() { + throw Exception_derived(); +} + +void second() { + try { + thrid() + } + catch (Exception_base const &obj) { + std::cout << obj.msg(); + throw; + } +} + +void first () { + try { + second(); + } + catch (Exception_derived const &obj) { + std::cout << obj.msg(); + } +} + +int main() { + first(); + return 0; +} + +\end{minted} + +\textbf{Вывод программы:} \\ +> base \\ +> derived \\ + +Значит тип объекта-параметра в текущем catch-блоке может отличаться от типа исключения, и это не влияет на дальнейшую обработку исключения в других catch-блоках. + + Например, это позволяет найти выход из такой ситуации: мы захотели вставить куда-то в глубь уже написанного кода try-catch-блок для логирования всех исключений. Тогда будем ловить по типу Exception\_all, который сделаем предком всех наший исключений. Ловим и пробрасывать дальше, чтобы не нарушать обработку производный от него исключений. + + \textcolor{red}{NB})Если необходимо изменить тип исключения, то мы можем в конце catch-блока сразу кинуть новое исключение нового типа. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int main() +{ + try { + try { + throw derived(); + } + catch (base const& e) { + throw e; + } + } + catch (base const& e) { ... } +} +\end{minted} + +\subsection{Function-try-block} + +Часто мы хотим, чтобы все тело функции находилось в try-блоке. Тогда это try-блок называется функциональным. И для него есть отдельный синтаксис. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int main() try { + //main's body +} +catch (...) { } +\end{minted} + +Здесь функциональные try-блоки являются синтаксическим сахаром, но есть ситуации когда без них не обойтись: обработка исключений в конструкторе. + +Вот есть класс, котором мы хотим ловить и обрабатывать исключения. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class St { +public: + St(): member() { + try { + // Constructor's code + } + catch (...) { } + } +private: + Member_type member; +} +\end{minted} + +Но заметим, что вызов конструкторов членов не находится внутри try-блока и исключения возникшие в их конструкторах не поймаются. +Поэтому мы используем здесь функциональный try-блок: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class St { +public: + St() try: member() { + // Constructor's code + } + catch (...) { + + } // implicit throw +} +private: + Member_type member; +} +\end{minted} + +Но у функциональный try-блок в конструкторах, есть особенность: они всегда бросают исключение повторно. + +\subsection{Уничтожение объекта при исключении в конструкторе} + +Также важно помнить, что если в конструкторе происходит исключение, то для него не вызовется деструктор, так как объект еще не считается созданным. Захваченные ресурсы придется очищать руками + +Важно понимать, что происходить в конструкторе когда возникает исключение. Если исключение возникает при созднии члена класса, то от всех уже созданных членов вызываются деструкторы. Но от самого объекта деструктор не вызвается, так как объект не считается созданным пока хотя бы один конструктор не отработал полностью. Поэтому при необходимости нужно либо руками освобождать захваченные ресурсы, либо декларировать небросающему конструктору. + +\subsection{Best practice} +Часто исключения применяются для корректной работы с ресурсами. То есть если возникает исключение и мы владеем какими-то ресурсам, то в случае генерации исключения следует их освободить. + +Например, мы пишем конструктор копирования для вектора, и нам необходимо скопировать данные в другой участок памяти. При этом если во время копирование какого-то объекта возникнет исключение, то хорошо если уже созданные объекты будут разрушены. Причем желательно в обратном порядке их создания. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void copy_construct(T* dist, T const * sourse, size_t size) { + size_t i = 0; + try { + for (; i != size; ++i) { + new (dist + i) T(sourse[i]); + } + } + catch (...) { // если ошибка при копировании + for (size_t j = i; j != 0; --j) { + dist[j - 1].~T(); // вызовем деструкторы созданных объектов + } + throw; + } +} +\end{minted} + +Полезно знать про стандартные исключения, такие как \mintinline{c++}{std::bad_alloc}, \mintinline{c++}{std::bad_cast}, \mintinline{c++}{std::bad_typeid} и т. д. Они также связанны наследованием и имеют общего предка \mintinline{c++}{std::exception}. + +Подробнее можно почитать здесь: \\ +\url{https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm} \\ +\url{http://en.cppreference.com/w/cpp/error/exception} \\ + +Хорошим тоном является наследование от \mintinline{c++}{std::exception}. + +Когда мы организовываем исключения в иерархии классов, то получаем мощный механизм описания исключение и способов их обработки. Создав такую структуры мы можем обрабатывать как более общие ошибки, так и более специализированные. +Пример: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + class StackException {}; + class popOnEmpty(): public StackException {}; + class pushOnFull(): public StackException {}; +\end{minted} + Причем сгенерировав исключение типа popOnEmpty(), мы можем в разных обработчиках независимо выбирать: обработать как popOnEmpty или как StackException, так как тип исключения не теряется при повторной генерации этого исключения. + +\subsection{Bad practice} +\begin{itemize} +\item +Плохо писать код бросающий исключения в catch-блоке. +\item +Хотя если исключений не происходит, то по скорости выполнения программа не сильно упадет, но иногда нужно учитывать большой overhead в случае возникновения и обработки исключения. +\end{itemize} + +\subsection{std::terminate()} + +Это функция, которая вызывается если у механизма исключений не получается корректно отработать, чтобы завершить программу. +Случаи когда она вызывается: +\begin{itemize} +\item Исключение брошено и не поймано ни одним catch-блоком, то есть пробрасывается вне main(). +\item Исключение бросается во время обработки другого исключения. Это может произойти только в catch-блоке или деструкторе. А также в функциях, которые вызываются ими. +\item Если функция переданная в \mintinline{c++}{std::atexit} и \mintinline{c++}{std::at_quick_exit} бросит исключение. +\item Если функция нарушит гарантии noexcept specification. Например, если функция помеченная как noexcept бросит исключение. +\item При подобных и не только ошибках в потоках. +\end{itemize} + +По умолчанию \mintinline{c++}{std::terminate()} вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистрировать ее как терминальную. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void my_terminate() { + cout << "It's not a bug, it's a feature!"; +} +/**/ +set_terminate(my_teminate); +\end{minted} diff --git a/Exception_and_RAII/RAII.tex b/Exception_and_RAII/RAII.tex new file mode 100644 index 0000000..df6d505 --- /dev/null +++ b/Exception_and_RAII/RAII.tex @@ -0,0 +1,197 @@ +\section{RAII-classes} +\subsection{Пример} +Начнем с примера. Пусть мы хотим открыть несколько файлов. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void read_some_file() { + open_for_read("first_part.txt"); + open_for_read("second_part.txt"); + open_for_read("third_part.txt"); +} +\end{minted} + + +Причем гарантировать их закрытие в случает возникновения исключения. +Тогда давайте напишем такой код: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void read_some_file() { + try { + open_for_read("first_part.txt"); + open_for_read("second_part.txt"); + open_for_read("third_part.txt"); + } catch (...) { + // Но мы не может понять какой файл открылся, какой нет. :( + } +} +\end{minted} + +Так как мы хотим запоминать какие файлы успели открыться, то перепишем так: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void read_some_file() { + size_t i = 0; + string files[3] = {"first_part.txt", "second_part.txt", "third_part.txt" }; + try { + for (; i < 3; ++i) { + open_for_read(files[i]); + } + } catch (...) { + for (size_t j = i; j != 0; --j) { + close(files[j - 1]); + } + } +} +\end{minted} + +Итого код увеличился в два раза. + +С помощью RAII-класса файловых дескрипторов Reader, можно упростить код: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void read_some_file() { + Reader reader1("first_part.txt"); + Reader reader2("second_part.txt"); + Reader reader3("third_part.txt"); +} +\end{minted} + +Этот получился довольно простой и безопасный. Почему безопасный? Потому, что объект reader открывает файл в конструкторе и закрывает в деструкторе. И если возникнет исключение, то при раскрутке стека удаляться все локальные объекты, в том числе и файловые дескрипторы, закрывая файлы. Если же какой-то файл не успел открыться, то не успел и создаться объект отвечающий за него. + +Это удобная идея получила название: RAII. + +\subsection{Определение} + +\textbf{<>} или <<Захват ресурса - это инициализация>> - это идиома класса, который инкапсулирует управление каким-то ресурсом. Она значит, что объект этого класса, получает доступ к ресурсу и удерживает его в течении своей жизни, а потом этот ресурс высвобождается при уничтожении объекта. +В конструкторе он должен захватить ресурс(открыть файл, выделить файл и т. д.), а в десткрукторе освободить его(закрыть файл, освободить память и т. д.). + +Также важно подумать, что должно происходить при копировании объекта, часто мы просто явно запрещаем это делать. + +\subsection{Что это дает?} +\begin{itemize} +\item Удобство кода: не нужно явно каждый раз в конце тела функции освобождать ресурсы и учитывать какие ресурсы успели захватиться, а какие нет. Когда выполнение текущего блока будет завершено, локальные объекты RAII-классов удалятся и необходимы ресурсы освободятся автоматически. +\item Безопасность исключений: Если вызывается исключение, то гарантируется, что стек очиститься и все локальные объекты удаляться, а значит и освободятся ресурсы. +\item Часто важно освобождать ресурсы в обратном порядке, относительно того, как они были захвачены. Это как раз поддерживается раскруткой стека при удалении локальных объектов. +\end{itemize} + +\textcolor{red}{NB}) Это идиома работает не только в С++, а любом языке с предсказуемым временем жизни объектов. + + +Вот небольшой пример RAII-класса, который будет управлять файлом. +Ресурсом является данные типа FILE (формат файла в Си). +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class File { +public: + File(char const *filename, char const *mode) + : _file(fopen(filename, mode)) { } //захват ресурса + + ~File() { + fclose(_file); // освобождение ресурса + } + + File(File const &) = delete; + File operator=(File const &) = delete; + +private: + FILE *_file; +}; +\end{minted} + +RAII часто встречается стандартной библиотеке: +\begin{itemize} +\item Smart pointers -- инкапсулируют несколько видов управления памятью +\item i/ofstream +\item Различные контейнеры +\end{itemize} + +\subsection{Best practice} + +Вернемся к предыдущему примеру File. +Пусть в конструкторе есть код, который может сгенерировать исключение, тогда возникает проблема освобождения ресурса. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +File::File(char const *filename, char const *mode) + : _file(fopen(filename, mode)) { + //код допускающий исключение +} +\end{minted} +Если в теле конструктора происходит исключение, то деструктор не вызовется, так как объект не считается созданным. Что делать? + +Решение № 1 +Написать try-catch +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class File { +public: + File(char const *filename, char const *mode) try + : _file(fopen(filename, mode)) { + //код допускающий исключение + } + catch (...) { + destruct_obj(); + } + + ~File() { + destruct_obj(); + } + +private: + void destruct_obj() { + fclose(_file); + } + FILE * _file; +}; +\end{minted} +Минусы решения: код раздулся. + +Решение № 2 +Можно сделать отдельный подкласс, который хранит в себе ресурс. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class File { + struct FileHandle { + FileHandle(FILE *fh) + : _fh(fh) { } + + ~FileHandle() { + fclose(_fh); + } + + FILE *_fh; + }; +public: + File(char const * filename, char const * mode) + : _file(fopen(filename, mode)) { + // код допускающий исключения + } + + ~File() = default; + +private: + FileHandle _file; +}; +\end{minted} + +Теперь все тоже хорошо, так как при возникновении исключения, вызовутся деструкторы от все членов класса. + +Решение № 3 (начиная с С++11) +Делегирующий конструктор. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +class File +{ + File(FILE * file) + : _file(file) { } + +public: + File(char const * filename, char const * mode) + : File(fopen(filename, mode)) { + // код допускающий исключения + } + + ~File() { + fclose(_file); + } + +private: + FILE *_file; +}; +\end{minted} + +Дело в том, что если мы в конструкторе вызываем другой конструктор, то после его выполнения объект считается созданным и уничтожится автоматически. diff --git a/Perfect_forwarding.tex b/Perfect_forwarding.tex new file mode 100644 index 0000000..8495eec --- /dev/null +++ b/Perfect_forwarding.tex @@ -0,0 +1,507 @@ +\section{Задача Perfect forwarding} + +\subsection{Формулировка проблемы, попытки тривиального решения} + + Пусть у нас есть функция g, принимающая параметр типа T. Мы хотим написать функцию f, которая примет тот же параметр типа T, сделает с ним что-нибудь (пусть в нашем примере f не делает ничего) и вызвать функцию g с этим аргументом. + + Примером пары таких f и g могут служить make\_unique(params) и конструктор T. + + make\_unique сначала вызовет конструктор T с заданными параметрами, а потом обернёт получившийся объект в unique\_ptr. В процессе передачи параметров объектов в конструктор нам и нужен perfect forwarding. + + Напишем первый вариант, который приходит на ум: + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void f(T x) + { + g(x); + } + \end{minted} + + Этот способ имеет недостатки. Рассмотрим случай, при котором g принимает параметры по неконстантной ссылке и меняет их. Тогда + + При вызове функции f мы проинициализируем x копией параметра вызова + + В g будет передана ссылка на копию исходного параметра + + В g произойдёт изменение копии исходного параметра, исходный параметр же останется неизменным. + + Это не то поведение, которого мы ожидаем, так как g должна изменить наш параметр. + + Другое решение: заставить f принимать параметр по константной ссылке + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void f(T const& x) + { + g(x); + } + \end{minted} + + Имеем почти ту же самую проблему: если g принимает параметр по неконстантной ссылке, вызов будет невозможен (так как неконстантная ссылка не может быть привязана к константной) + + Третье решение: заставим f принимать параметры по неконстантной ссылке + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void f(T& x) + { + g(x); + } + \end{minted} + + Это будет проблемой при попытке вызвать f для const-объектов или для rvalue, то есть вызов f(5) и f(some\_function()) станет невозможен. + + Ни одно из приведённых выше решений не является универсальным. Возможно ли написать функцию f, работающую во всех случаях? + + Можно сделать две перегрузки f: для константных и неконстантных ссылок. + + \vspace{\baselineskip} + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void f(T const& x) + { + g(x); + } + + template + void f(T& x) + { + g(x); + } + \end{minted} + + На первый взгляд это решение кажется хорошим, но очень скоро мы понимаем, что тогда в f нужно будет написать в два раза больше кода. К тому же, если g и f принимают по два параметра, придётся писать уже четыре перегрузки: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T& a, T& b) + +template +void f(T const& a, T& b) + +template +void f(T& a, T const& b) + +template +void f(T const& a, T const& b) +\end{minted} + +для трёх параметров - 8, а для n - $2^n$. Поэтому этот способ не применим для большого количества аргументов. + + Прежде чем рассматривать грамотное C++ 11 решение этой проблемы, давайте ближе познакомимся с правилами вывода ссылок в C++ 11. + + +\subsection{reference collapsing rule} + + Как известно, в C++ не существует ссылок на ссылки. В тех случаях, когда возникает такой тип, ссылки схлопываются (происходит reference collapsing) и получается просто одна ссылка. Например: + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template f(T & x); + + int x = 5; + f(x); + \end{minted} + + В качестве T подставляется int\&. При подстановке T -> int\& в T\&, должно была бы получится ссылка на ссылку int\& \&, но ссылки схлопываются и получается просто int\&. Следовательно сигнатура функции f после подстановки выглядит следующим образом: f(int\& x). + + Неформально правило reference collapsing в C++03 можно записать в следующем виде \& + \& = \& + + С появлением в C++11 rvalue-ссылок потребовалось доопределить правила reference collapsing для них. Эти правила могут быть неформально записаны как “одиночный амперсант всегда побеждает”. Таким образом, таблица reference collapsing в C++ 11 выглядит так: + + \& + \& = \& + + \& + \&\& = \& + + \&\& + \& = \& + + \&\& + \&\& = \&\& + + Приведём несколько примеров этого правила: + +\begin{center} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T & x); + +int y; +f(y); +\end{minted} +& +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T && x); + +f(some_function()); +//some_function возвращает int +\end{minted} +\end{tabular} + +Тогда при инстанцировании шаблона T -> int\&\&, сигнатура функции имеет вид + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int&& & x); +\end{minted} +& +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int&& && x); +\end{minted} +\end{tabular} + +После reference collapsing она превращается в: + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int&& x); +\end{minted} +& +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int&& x); +\end{minted} +\end{tabular} +\end{center} + +\subsection{Правила особого вывода ссылок} + + Пусть есть конструкция + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void func(T&& x); + \end{minted} + + Пусть есть вызов func(expression), expression имеет тип E. Тогда если expression, является lvalue, T выводится как E\&, если же expression является rvalue, то T выводится как E. + + Примеры: + + +\begin{center} +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +lvalue: & rvalue:\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{double pi = 3.14;} +& \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{ +func(pi);} +& +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{ +func(4);}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{ +pi - lvalue типа double, T -> double& , сигнатура func: } +& +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{ +4 - rvalue типа int, тогда T -> int, сигнатура func: } \\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{ +void func(double& &&);} +& +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{void func(int&&);} \\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +после reference collapsing & \vspace{\baselineskip} \\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\mintinline[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}{ +void func(double&); +} & \vspace{\baselineskip} \\ +\end{tabular} + +\end{center} + + + \vspace{\baselineskip} + + Отметим, что переменная типа T\&\&, использованная в функции выше, не всегда является rvalue-ссылкой, и называть её rvalue-ссылкой некорректно. В книге Скота Майерса “Эффективный и современныи С++” эти ссылки названы универсальными. Причины такого наименования станут ясны далее. + + Эти правила применяются для реешения проблемы perfect forwarding. + +\subsection{Решение проблемы perfect forwarding} + + Рассмотрим код способы передачи аргумента в g: + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void f(T&& x) + { + //Как можно передать x в g? + g(x); //передать как lvalue + g(std::move(x)); //передать как rvalue + } + + \end{minted} + + Ни один ни другой способ не подходит. x должно передаваться так же, как получается, то есть поставлена задача сохранения value-category: пришедшее как rvalue значение, должно быть передано как rvalue, а пришедшее как lvalue - как lvalue. + + Рассмотрим вспомогательную функцию, с помощью которой мы будем это делать. + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + U&& forward(U& a) + { + return static_cast(a); + } + \end{minted} + + Тогда код, использующий её, будет выглядеть так + + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + template + void f(T&& a) + { + g(forward(a)); + } + \end{minted} + + Данная функция реализует perfect forwarding. Чтобы убедиться в этом рассмотрим процесс вызова f для rvalue и lvalue и посмотрим на процесс вывода и подстановки аргументов шаблона. + +\begin{center} +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +lvalue: & rvalue:\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +int a; & \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +f(a); & f(5);\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +a - lvalue типа int, поэтому T выводится в int\&, тогда после подстановки, до применения reference collapsing f выглядит так: & 5 - rvalue типа int, следовательно Т выводится в int, а после подстановки f выглядит так:\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int& && a) +{ + g(forward(a)); +} +\end{minted} +& +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int&& a) +{ + g(forward(a)); +} +\end{minted} +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +После применения reference collapsing, она будет выглядеть так: +& \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int& a) +{ + g(forward(a)); +} +\end{minted} +& \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +В forward в качестве U передают int\& (явно, в треугольных скобочках). Таким образом после подстановки forward выглядит так: + & В forward в качестве U передают int (явно, в треугольных скобочках), и после подстановки forward выглядит так: +\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int& && forward(int& & a) +{ + return static_cast(a); +} +\end{minted} + & \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int&& forward(int& a) +{ + return static_cast(a); +} + +\end{minted} +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +После reference collapsing: & \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int& forward(int& a) +{ + return static_cast(a); +} + +\end{minted} + & \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +Заметим, что эта функция не делает ничего, а просто кастит int\& к int\&. Поскольку forward возвращает int\&, то результат его вызова - lvalue.Значит f передаёт свой аргумент в g как lvalue. Заметим, что изначально в f a пришла как lvalue. Значит, для lvalue передача работает корректно. & Заметим, что в данном случае функция forward работает как move(приводит lvalue-ссылку к rvalue-ссылке). Результат вызова функции forward является rvalue, то есть f передаёт a как rvalue. Заметим, что получали мы её тоже как rvalue, значит, для rvalue передача работает корректно.\\ +\end{tabular} + +\end{center} + + Итого, мы сохранили value-category. forward работает либо как ничего (если ей передано lvalue), либо как move(если ей передано rvalue). + +\subsection{Одна частая ошибка при использовани forward} + Иногда при вызове forward забывают указать template-параметр в треугольных скобках. Выглядит это так: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void f(T&& a) +{ + g(forward(a)); +} + + +\end{minted} + + Покажем, почему это ошибка и как от неё избавиться. Расмотрим вызовы для lvalue и rvalue. + +\begin{center} +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +lvalue: & rvalue:\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +int a; & \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +f(a); & f(5);\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +a - lvalue типа int, поэтому T выводится в int\&, тогда после подстановки, до применения reference collapsing f выглядит так: & 5 - rvalue типа int, следовательно Т выводится в int, а после подстановки f выглядит так:\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int& && a) +{ + g(forward(a)); +} +\end{minted} +& +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int&& a) +{ + g(forward(a)); +} + +\end{minted} +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +После применения reference collapsing, она будет выглядеть так: +& \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void f(int& a) +{ + g(forward(a)); +} +\end{minted} +& \vspace{\baselineskip}\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +Типы для forward выводятся следующим образом: +a имеет тип int\&, а forward принимает U\&, таким образом, U выводится как int. Тогда forward выглядит следующим образом:: + & U выводится как int\&\&, тогда вместо U\& подставляется int\&\& \& (сжимается в int\&\&), а вместо U\&\& подставляется int\&\& \&\& (сжимается в int\&\&) Тогда forward выглядит следующим образом: +\\ +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int&& forward(int& a) +{ + return static_cast(a); +} + +\end{minted} + & \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +int&& forward(int& a) +{ + return static_cast(a); +} + + +\end{minted} +\end{tabular} + +\begin{tabular}{p{0.4\linewidth}p{0.4\linewidth}} +То есть forward для lvalue работает как move, а должен рабоать как ничего. & Таким образом, для rvalue forward тоже сработает как move.\\ +\end{tabular} + +\end{center} + + Мы выяснили, что, если в forward не указать параметр в треугольных скобках, то он всегда будет работать как move. + + Как можно решить эту проблему? Нужно запретить выводить тип для forward, а позволить только явно указывать его. Давайте запретим вывод типа в forward. Рассмотрим вспомогательный класс. + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +struct no_deduce +{ + typedef T type; +} +\end{minted} + + Запишем новую версию forward: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +U&& forward(typename no_deduce::type& a) +{ + return static_cast(a); +} +\end{minted} + + Заметим, что typename no\_deduce::type\& эквивалентно U\&. + + Эта конструкция запрещает вывод типов, так как при передаче значения в функцию нам нужно вывести не U\&, а typename no\_deduce::type\&, а это невыводимый контекст. Компилятор может определить, каким типом должен быть no\_deduce::type (пусть он должен иметь тип E), но он не способен вывести тип U, чтобы no\_deduce::type имел тип E (В данном конкретном случае это сделать можно (U должен совпадать с E), но в общем случае такая задача неразрешима, поэтому компилятор C++ не выводит типы в таких случаях). + + Проблема решена. + + Отметим напоследок, что forward есть в стандартной библиотеке. + +\subsection{Использование perfect forwarding} + + Perfect forwarding используется, например, при использовании функций высшего порядка - то есть функций, принимающих другие функции в качестве аргументов или возвращающих их в качестве возвращаемого значения. + + Без perfect forwarding, применение функций высшего порядка довольно обременительно, так как нет удобного способа передать аргументы в функцию внутри функции-обертки. + Возвращаясь к примеру, изложенному в начале главы, функцию-обёртку над конструктором типа T make\_unique с использованием perfect forwarding можно реализовать следующим образом: + +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +//тип T - тип, обёртываемый в shared_ptr, Args - типы аргументов конструктора. +shared_ptr make_unique(Args&&... args) +{ + return unique_ptr(new T(std::forward(args)...)) +} +\end{minted} + + Про используемые в коде variadic template можно подробнее узнать в следующей главе. diff --git a/main.tex b/main.tex index 8e24d4e..61de90c 100644 --- a/main.tex +++ b/main.tex @@ -44,5 +44,8 @@ \include{preprocessor} \include{nullptr} \include{rvalue-references} - +\include{Exception_and_RAII/Exception} +\include{Exception_and_RAII/Exception-safety} +\include{Exception_and_RAII/RAII} +\include{Perfect_forwarding} \end{document} From 636e810917f66537db4918a86aa1fdd22aa26b52 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Wed, 27 Dec 2017 21:31:40 +0300 Subject: [PATCH 22/30] editorial for exceptions --- Exception_and_RAII/Exception.tex | 44 ++++++++++++-------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex index 3e99756..22b3e2f 100644 --- a/Exception_and_RAII/Exception.tex +++ b/Exception_and_RAII/Exception.tex @@ -1,8 +1,8 @@ \section{Exceptions} \subsection{Введение} -Часто нехватка динамической памяти, неправильный ввод пользователя, ошибка с файловой системой, приводят к тому, что продолжение исполнения логики программы не возможно. Например, если наша функция foo вызывает malloc и malloc вернул ошибку, то функция foo должна завершится и тоже вернуть ошибку. Возможно, что вызывающая сторона функции foo, тоже проверит возвращаемое значение функции foo и завершится с ошибкой. +Часто нехватка динамической памяти, неправильный ввод пользователя, ошибка с файловой системой, приводят к тому, что продолжение исполнения логики программы невозможно. Например, если наша функция foo вызывает malloc и malloc вернул ошибку, то функция foo должна завершиться и тоже вернуть ошибку. Возможно, что функция, вызывающая функцию foo, тоже проверит возвращаемое значение и завершится с ошибкой. -В C это поведение достигалось, явной проверкой возвращаемого значения функции с помощю if и return'а в случае ошибки. C++ имеет встроенный в язык механизм поддержки такого поведения. Он называется механизмом исключений. +В C такая поведение реализовывалось, явной проверкой возвращаемого значения функции с помощю if и исполнением return'а в случае ошибки. C++ имеет встроенный в язык механизм поддержки такого поведения. Этот механизм называется механизмом исключений. Рассмотрим следующую функцию деления: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -14,10 +14,10 @@ \subsection{Введение} Если в эту функцию передать в качестве $b$ $0$, то произойдет undefined behavior. Предположим, что мы хотим, чтобы функция сообщала об ошибке, когда $b = 0$. Для этого сначала необходимо объявить класс исключения, объекты которого будут хранить в себе информацию об исключении. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -class Division_by_zero() { +class Division_by_zero { int dividend string message; - Division_by_zero(int ÷nd, string const &message) : + Division_by_zero(int dividend, string const &message) : dividend(dividend), message(message) { } }; \end{minted} @@ -32,13 +32,13 @@ \subsection{Введение} } \end{minted} -Оператор throw завершает исполнение текущей функции и возвращает ошибку в вызывающую функцию. Вызывающая сторона может обрабатывать исключение следующим образом: +Оператор throw завершает исполнение текущей функции и возвращает ошибку в вызывающую функцию. Вызывающая сторона может обработать исключение следующим образом: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int main() { int n; cin >> n; - try { // здесь указываем опрераторы, в которых мы хотим ловить исключения. + try { // здесь указываем операторы, в которых мы хотим ловить исключения. for (int i = 0, a, b; i < n; ++i) { cin >> a >> b; cout << div(a, b); @@ -55,17 +55,7 @@ \subsection{Введение} Если исключения не возникает, то цикл for отработает до конца, catch-блок вызван не будет. \subsection{Описание конструкций} -Рассмотрим используемые конструкции подробнее: - -\mintinline{c++}{try{}} -- защищенный блок. Здесь пишется код, исключения в котором необходимо ловить и обрабатывать. - -\mintinline{c++}{catch(){}} -- блок перехвата исключений или блок обработки или обработчик. Здесь будут ловиться исключения, тип которых совпадает по определенным правилам с типом указанным в (), и обрабатываться инструкциями в \{\} - -\mintinline{c++}{throw} -- оператор генерирует исключение. (Иногда говорят, "бросает"\ или "выбрасывает"\ исключение) - -Теперь давай рассмотрим детали работы этого механизма. - -Блок \mintinline{c++}{try-catch} используется для обработки исключений. И имеет общий вид: +Рассмотрим используемые конструкции подробнее. Блок \mintinline{c++}{try-catch} используется для обработки исключений и имеет общий вид: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} try { /*операторы защищенного блока*/ } // catch-блоки @@ -75,21 +65,21 @@ \subsection{Описание конструкций} catch(exceptionN const& e) {/*код обработки*/} \end{minted} -Если изнутри блока try вылетит исключение то, компилятор попытается подобрать подходящий catch-блок. В catch блоке указывается какие действия необходимо сделать, при возникновении исключения указанного типа. +Внутри блока try пишется код, исключения в котором необходимо ловить и обрабатывать. Если изнутри блока try вылетит исключение то, компилятор попытается подобрать подходящий catch-блок. В catch блоке указывается какие действия необходимо сделать, при возникновении исключения указанного типа. Catch-блок может иметь две формы: \begin{itemize} \item - \mintinline{c++}{catch(/*declaration*/) { /*обработчик исключения*/ }} Ловит исключение указанного или производных от него типов. Переменной исключения можно дать имя. Это позволяет обращаться к объекту исключения. + \mintinline{c++}{catch(/*declaration*/) { /*обработчик исключения*/ }} Ловит исключение указанного или производных от него типов. Переменной исключения можно дать имя. Это позволяет обращаться к пойманому объекту исключения. \item \mintinline{c++}{catch(...) { /*обработчик исключения*/ }} - Ловит исключения всех типов. В этом случае невозможно обращаться к объекту исключения. + Ловит исключения всех типов. В этом случае обращаться к объекту исключения невозможно. \end{itemize} -Что происходит, когда мы генерируем исключение: +Оператор \mintinline{c++}{throw} генерирует исключение. (Иногда говорят, "бросает"\ или "выбрасывает"\ исключение). При генерации исключения происходит следующее: \begin{enumerate} \item - Создается копия объекта переданного в оператор throw. Этот объект будет существовать до тех пор, пока исключение не будет обработано. Если тип объекта имеет конструктор копирования, то он будет вызван. + Создается копия объекта переданного в оператор throw. Эта копия будет существовать до тех пор, пока исключение не будет обработано. Если тип объекта имеет конструктор копирования, то для создания копии будет использован конструктор копирования. \item Прерывается исполнение программы. \item @@ -169,13 +159,13 @@ \subsection{Как ловится исключение?} } }; -void thrid() { +void third() { throw Exception_derived(); } void second() { try { - thrid() + third() } catch (Exception_base const &obj) { std::cout << obj.msg(); @@ -269,13 +259,13 @@ \subsection{Function-try-block} } \end{minted} -Но у функциональный try-блок в конструкторах, есть особенность: они всегда бросают исключение повторно. +У функциональных try-блоков в конструкторах, есть особенность: они всегда бросают исключение повторно. \subsection{Уничтожение объекта при исключении в конструкторе} -Также важно помнить, что если в конструкторе происходит исключение, то для него не вызовется деструктор, так как объект еще не считается созданным. Захваченные ресурсы придется очищать руками +Если возникновении исключения в конструкторе некоторого объекта, объект не считается созданным и деструктор для него не вызывается. Если конструктор захватывает некоторые ресурсы и потом бросается исключение, то конструктору следует самостоятельно освободить эти ресурсы перед выбрасыванием исключения. -Важно понимать, что происходить в конструкторе когда возникает исключение. Если исключение возникает при созднии члена класса, то от всех уже созданных членов вызываются деструкторы. Но от самого объекта деструктор не вызвается, так как объект не считается созданным пока хотя бы один конструктор не отработал полностью. Поэтому при необходимости нужно либо руками освобождать захваченные ресурсы, либо декларировать небросающему конструктору. +При исполнении конструктора класса, вызываются конструкторы всех членов этого класса. Если исключение возникает при создании одного из членов класса, то в процессе раскрутки стека будут вызваны деструкторы от всех уже созданных членов. У самого объекта деструктор не вызвается, так как объект не считается созданным пока его конструктор не отработал полностью. \subsection{Best practice} Часто исключения применяются для корректной работы с ресурсами. То есть если возникает исключение и мы владеем какими-то ресурсам, то в случае генерации исключения следует их освободить. From 4c49cc4f1a66e0243a9828360a22ec24d29105cc Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Wed, 27 Dec 2017 21:50:18 +0300 Subject: [PATCH 23/30] more TODOs --- Exception_and_RAII/Exception.tex | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex index 22b3e2f..27e46ec 100644 --- a/Exception_and_RAII/Exception.tex +++ b/Exception_and_RAII/Exception.tex @@ -165,7 +165,7 @@ \subsection{Как ловится исключение?} void second() { try { - third() + third(); } catch (Exception_base const &obj) { std::cout << obj.msg(); @@ -192,6 +192,7 @@ \subsection{Как ловится исключение?} \textbf{Вывод программы:} \\ > base \\ > derived \\ +% TODO: я даже специально скомпилировал эту программу. Она выводит derivedderived Значит тип объекта-параметра в текущем catch-блоке может отличаться от типа исключения, и это не влияет на дальнейшую обработку исключения в других catch-блоках. @@ -268,6 +269,9 @@ \subsection{Уничтожение объекта при исключении в При исполнении конструктора класса, вызываются конструкторы всех членов этого класса. Если исключение возникает при создании одного из членов класса, то в процессе раскрутки стека будут вызваны деструкторы от всех уже созданных членов. У самого объекта деструктор не вызвается, так как объект не считается созданным пока его конструктор не отработал полностью. \subsection{Best practice} +% TODO: не должно ли это относится к exception-safety? +% TODO: первая фраза неправильная: не "исключения применяются для корректной работы с ресурсами", более правильно будо бы сказать +% что-то вроде "корректная работа с ресурсами должна учитывать, то это существуют исключения". Часто исключения применяются для корректной работы с ресурсами. То есть если возникает исключение и мы владеем какими-то ресурсам, то в случае генерации исключения следует их освободить. Например, мы пишем конструктор копирования для вектора, и нам необходимо скопировать данные в другой участок памяти. При этом если во время копирование какого-то объекта возникнет исключение, то хорошо если уже созданные объекты будут разрушены. Причем желательно в обратном порядке их создания. @@ -298,6 +302,7 @@ \subsection{Best practice} Хорошим тоном является наследование от \mintinline{c++}{std::exception}. Когда мы организовываем исключения в иерархии классов, то получаем мощный механизм описания исключение и способов их обработки. Создав такую структуры мы можем обрабатывать как более общие ошибки, так и более специализированные. +% TODO: согласен, можно даже упомянуть про использование виртуального наследования для исключений Пример: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} class StackException {}; @@ -309,9 +314,9 @@ \subsection{Best practice} \subsection{Bad practice} \begin{itemize} \item -Плохо писать код бросающий исключения в catch-блоке. +Плохо писать код бросающий исключения в catch-блоке. % TODO: почему? \item -Хотя если исключений не происходит, то по скорости выполнения программа не сильно упадет, но иногда нужно учитывать большой overhead в случае возникновения и обработки исключения. +Хотя если исключений не происходит, то по скорости выполнения программа не сильно упадет, но иногда нужно учитывать большой overhead в случае возникновения и обработки исключения. % TODO: не надо не так писать. Надо сказать: "код использующий исключения такой же быстрый как код без исключений, если исключения не возникают, но если они возникают то в современных реализациях они как правило дороже проверок на коды возврата, поэтому не рекомендуется использовать исключения как часть обычного control-flow". \end{itemize} \subsection{std::terminate()} @@ -320,13 +325,13 @@ \subsection{std::terminate()} Случаи когда она вызывается: \begin{itemize} \item Исключение брошено и не поймано ни одним catch-блоком, то есть пробрасывается вне main(). -\item Исключение бросается во время обработки другого исключения. Это может произойти только в catch-блоке или деструкторе. А также в функциях, которые вызываются ими. +\item Исключение бросается во время обработки другого исключения. Это может произойти только в catch-блоке или деструкторе. А также в функциях, которые вызываются ими. % TODO: А почему могут какие-то проблемы в catch-блоке возникнуть? \item Если функция переданная в \mintinline{c++}{std::atexit} и \mintinline{c++}{std::at_quick_exit} бросит исключение. \item Если функция нарушит гарантии noexcept specification. Например, если функция помеченная как noexcept бросит исключение. -\item При подобных и не только ошибках в потоках. +\item При подобных и не только ошибках в потоках. % TODO: при подобных при каких? очень странная фраза, если хочется сказать, что то, что из главной функции потока нельзя выбрасывать, то её можно даже объединить с пунктом про main. \end{itemize} -По умолчанию \mintinline{c++}{std::terminate()} вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистрировать ее как терминальную. +По умолчанию \mintinline{c++}{std::terminate()} вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистрировать ее как терминальную. % TODO: Пример интересный, но ошибочный. my_terminate не может возвращаться. Она должна завершить исполнение программы. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void my_terminate() { cout << "It's not a bug, it's a feature!"; From 55ebe8e593e4b193b66fb56e70ce6d8670c5174a Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Wed, 27 Dec 2017 22:27:28 +0300 Subject: [PATCH 24/30] Exception-safety review --- Exception_and_RAII/Exception-safety.tex | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Exception_and_RAII/Exception-safety.tex b/Exception_and_RAII/Exception-safety.tex index 6585c09..0399430 100644 --- a/Exception_and_RAII/Exception-safety.tex +++ b/Exception_and_RAII/Exception-safety.tex @@ -15,17 +15,23 @@ \subsection{Мотивирующий пример} \end{minted} -Это код не использует исключения, то есть не вызывает их и не обрабатывает. Но если будет ошибка выделения памяти при расширении вектора, то исключение возникнет в функции push\_back(), потом пробросятся через resize() наружу. +Это код не использует исключения, то есть не генерирует их и не ловит. Но если будет ошибка выделения памяти при расширении вектора, то исключение возникнет в функции push\_back(), потом пробросятся через resize() наружу. То есть проблема в том, что мы не используя механизм исключений все равно можем получить от него проблемы. Как например, не пойманное исключение. +% TODO: Все замечательно, но не сказано в чем собственно проблема. Проблема в том, что мы видимо хотим, чтобы либо объект не менялся (при исключении) либо менялся в нужное состояние (при отсутствии исключения) т.е. строгую гарантию. Но совсем не понятно почему мы это хотим. Пример с copy_construct в Exception.tex был гораздо показательнее, там просто всё в хлам ломалось. \subsection{Определение} Поэтому существуют Гарантии безопасности исключений (Exception safety). Это некий контракт исключений, который представляет из себя ряд уровней безопасности, которые присваиваются всем методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. +% TODO: Определение сложное. Она даже наверное корректое, просто из него не совсем понятно о чем это. +% Я предлагаю дать определение на примере: +% Пусть у нас есть объект типа std::vector, и я вызываю у него функцию push_back. +% Функция push_back бросила эксепшен, что я после этого могу знать о состоянии vector'а? +% И я предлагаю перечислять начиная с самой сильной, потому, что когда читаешь про самые слабые не понятно зачем они вообще нужны. А так понятнее, что это просто ослабление сильных гарантий в тех случаях когда их предоставить не получается. Уровни гарантий: \begin{enumerate} -\item \textbf{<>} - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя. +\item \textbf{<>} - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя. % TODO: No guarantees это не совсем гарантия, это я бы сказал её отсутствие. В корректных программах такое иметься не должно. Я бы даже его отдельно написал. \item \textbf{<>} - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. \item \textbf{<>} - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. \item \textbf{<>} - Кроме базовой гарантии, гарантируется, что исключения не возникают. @@ -35,9 +41,9 @@ \subsection{Основные моменты} Теперь давайте рассмотрим важные моменты: \begin{itemize} -\item Методы пользовательского интерфейса должны удовлетворять базовым или строгим гарантиям. Это избавляет пользователей от утечек памяти и инвалидных данных. +\item Методы пользовательского интерфейса должны удовлетворять базовым или строгим гарантиям. Это избавляет пользователей от утечек памяти и инвалидных данных. % TODO: А Nothrow guarantee можно? Я бы сказал так, в корректных программах все функции должны иметь какую-то гарантию безопасности исключений либо не использовать исключения. -\item Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. +\item Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. % TODO: непонятно \item Важность гарантии nothrow: Она есть у очень небольшого количества функции: swap, vector::pop\_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. From 14ee116c937963037101523b4db068e06590b84b Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Fri, 29 Dec 2017 13:57:30 +0300 Subject: [PATCH 25/30] typo --- Exception_and_RAII/Exception.tex | 2 +- how-to-contribute.tex | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex index 3e99756..3419d1d 100644 --- a/Exception_and_RAII/Exception.tex +++ b/Exception_and_RAII/Exception.tex @@ -207,7 +207,7 @@ \subsection{Как ловится исключение?} Например, это позволяет найти выход из такой ситуации: мы захотели вставить куда-то в глубь уже написанного кода try-catch-блок для логирования всех исключений. Тогда будем ловить по типу Exception\_all, который сделаем предком всех наший исключений. Ловим и пробрасывать дальше, чтобы не нарушать обработку производный от него исключений. - \textcolor{red}{NB})Если необходимо изменить тип исключения, то мы можем в конце catch-блока сразу кинуть новое исключение нового типа. + \textcolor{red}{NB})Если необходимо изменить тип исключения, то мы можем в конце catch-блока сразу кинуть новое исключение нового типа. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} int main() diff --git a/how-to-contribute.tex b/how-to-contribute.tex index b3ee36d..e3fd844 100644 --- a/how-to-contribute.tex +++ b/how-to-contribute.tex @@ -64,31 +64,31 @@ \section{Работа с Git} Для того, чтобы работать над конспектом необходимо использовать git. Поэтому я привел несколько ситуации, в которые вы неизбежно попадете. -{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной репрезиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной репрезиторий ({\bf pull request}). - \subsection{Начало работы с основным репрезиторем} +{\bf Суть:} \url{https://github.com/sorokin/cpp-notes.git} - это основной репозиторий, на котором лежит релиз конспекта. Чтобы редактировать или создавать статьи, нужно копировать его к себе на git ({\bf forked}), а потом залить в основной репозиторий ({\bf pull request}). + \subsection{Начало работы с основным репозиторем} \begin{enumerate} - \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репрезитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) + \item сделать fork: на \href{https://github.com/sorokin/cpp-notes}{странице} основного репозитория вверху справа будет нужная кнопка. (рядом с "Watch"\ ) - Теперь ответвление репрезитория будет у вас на гите. Вы просто работает с ним как со своим. + Теперь ответвление репозитория будет у вас на гите. Вы просто работает с ним как со своим. \item делаем clone на свой компьютер: \begin{minted}{bash} git clone git://github.com/my_name/cpp-notes.git \end{minted} \end{enumerate} \subsection{Работа с ответвлением} - Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репрезиторий. + Тут все просто: меняем, делаем коммит, потом push. Ведь это ваш репозиторий. \textcolor{red}{NB}) Для тех, кому не все просто, отправляю учить \href{https://git-scm.com/book/ru/v1}{основы git} - \subsection{Залив в основной репрезиторий} - Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения предложатся создателю репрезетория. + \subsection{Залив в основной репозиторий} + Сначала нужно залить все изменения на свой git. Потом нужно кнопку \textbf{New pull request} и после этого ваши изменения отправятся создателю репрезетория. - \subsection{Добавление изменений основного репрезитория в ваш} - Рассмотрим такую ситуацию: вы работаете над статьей. И в это время, кто-то меняет основной репрезиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? + \subsection{Добавление изменений основного репозитория в ваш} + Рассмотрим такую ситуацию: вы работаете над статьей. И в это время, кто-то меняет основной репозиторий. Как вам получить эти изменения и не потерять то, что вы уже сделали? Решение: \begin{minted}[breaklines]{bash} - git remote add sorokin "https://github.com/sorokin/cpp-notes.git" # добавим основной репрезиторий в remote + git remote add sorokin "https://github.com/sorokin/cpp-notes.git" # добавим основной репозиторий в remote git remote -v # убедимся, что он добавился в remote git fetch sorokin # сделаем из него ветку git branch -v # убедимся, что появилась новая ветка со всем нужными нами изменениями @@ -97,7 +97,7 @@ \section{Работа с Git} \end{minted} -\textcolor{red}{NB}) После того, как мы получили ветку основного репрезитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. +\textcolor{red}{NB}) После того, как мы получили ветку основного репозитория, мы можем делать с ней все, что хотим. В принципе, можно делать rebase. \textcolor{red}{NB}) Если кто не силен в ветвлении, смотрите все туже \href{https://git-scm.com/book/ru/v1}{книгу}. From 516fd46b046cf2c9e855561487836270c50e92f4 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Fri, 29 Dec 2017 20:20:04 +0300 Subject: [PATCH 26/30] fixup: rewrite exception-safety and some todo --- Exception_and_RAII/Exception-safety.tex | 104 +++++++++++++++++------- Exception_and_RAII/Exception.tex | 45 ++++++---- Exception_and_RAII/RAII.tex | 15 ++-- how-to-contribute.tex | 11 ++- 4 files changed, 117 insertions(+), 58 deletions(-) diff --git a/Exception_and_RAII/Exception-safety.tex b/Exception_and_RAII/Exception-safety.tex index 0399430..d30402d 100644 --- a/Exception_and_RAII/Exception-safety.tex +++ b/Exception_and_RAII/Exception-safety.tex @@ -1,56 +1,104 @@ \section{Exception safety} \subsection{Мотивирующий пример} -Пусть у нас есть функция \mintinline{c++}{vector::resize()}; +Рассмотрим функцию копирования данных в векторе. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +template +void vector::operator=(vector const& other) { + data = new char(sizeof(T) * other.size()); + for (size_t i = 0; i != other.size(); ++i) { + new (data + i) T(other[i]); + } +} +\end{minted} + +На первый взгляд это довольно безобидный код. Но он совершено не учитывает возможность возникновения исключения в одном из вызовов конструктора \mintinline{c++}{T()}. Что произойдет в этом случаи? + +Исключение пролетит дальше. Предположим, что его кто-то поймал. Но тогда он обнаружит наш вектор в сломанном состоянии. И не сможет ничего с ним сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушены инварианты класса. +Напишем аккуратнее. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void vector::resize(size_t size) { - if (size > curr_size) { - cnt_add = size - curr_size; // количество элементов для добавления. - for (size_t i = 0; i < cnt_add; ++i) { - push_back(T()); +template +void vector::operator=(vector const& other) { + size_t i = 0; + try { + data = new char[sizeof(T) * other.size()]; + for (size_t i = 0; i != other.size(); ++i) { + new (data + i) T(other[i]); + } + } + catch(...) { // если ошибка при копировании + for (size_t j = i; j != 0; --j) { + data[j - 1].~T(); // вызовем деструкторы созданных объектов } + delete[] data; + data = nullptr; } } \end{minted} +Если исключения не возникнет то функция скопирует данные, иначе data занулится, что будет соответствовать пустому вектору. И мы сохранили инварианты, то есть класс остался в корректном состоянии и пользователь может вызывать public-методы. -Это код не использует исключения, то есть не генерирует их и не ловит. Но если будет ошибка выделения памяти при расширении вектора, то исключение возникнет в функции push\_back(), потом пробросятся через resize() наружу. +Например: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +vector a, b; +/* +* some code +*/ +try { + a = b +} catch(...) { + if (a.size() == 0) { + std::cout << "Bad copy" << std::endl; + } +} +\end{minted} -То есть проблема в том, что мы не используя механизм исключений все равно можем получить от него проблемы. Как например, не пойманное исключение. -% TODO: Все замечательно, но не сказано в чем собственно проблема. Проблема в том, что мы видимо хотим, чтобы либо объект не менялся (при исключении) либо менялся в нужное состояние (при отсутствии исключения) т.е. строгую гарантию. Но совсем не понятно почему мы это хотим. Пример с copy_construct в Exception.tex был гораздо показательнее, там просто всё в хлам ломалось. +Давайте обобщим этот пример: + +Часто мы хотим, что в случае возникновения исключение в коде какой-то структуры данных инварианты класса сохранялись, чтобы пользователь мог корректно обработать вылетевшие исключения. + +Это идея развилась в Гарантии безопасности исключений. \subsection{Определение} -Поэтому существуют Гарантии безопасности исключений (Exception safety). Это некий контракт исключений, который представляет из себя ряд уровней безопасности, которые присваиваются всем методам класса. Они декларируют выполнение некоторого контракта относительно состояния объекта после выполнения операций над ним. -% TODO: Определение сложное. Она даже наверное корректое, просто из него не совсем понятно о чем это. -% Я предлагаю дать определение на примере: -% Пусть у нас есть объект типа std::vector, и я вызываю у него функцию push_back. -% Функция push_back бросила эксепшен, что я после этого могу знать о состоянии vector'а? -% И я предлагаю перечислять начиная с самой сильной, потому, что когда читаешь про самые слабые не понятно зачем они вообще нужны. А так понятнее, что это просто ослабление сильных гарантий в тех случаях когда их предоставить не получается. +Гарантии безопасности исключений (\textit{англ.} Exception safety) -- это контракт для методов класса относительно исключений. Уровни гарантий: \begin{enumerate} -\item \textbf{<>} - нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя. % TODO: No guarantees это не совсем гарантия, это я бы сказал её отсутствие. В корректных программах такое иметься не должно. Я бы даже его отдельно написал. -\item \textbf{<>} - Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. -\item \textbf{<>} - Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. -\item \textbf{<>} - Кроме базовой гарантии, гарантируется, что исключения не возникают. +\item \textbf{<>} -- Гарантируется, что инварианты класса сохраняются и не происходит утечек памяти или других ресурсов. +\item \textbf{<>} -- Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. +\item \textbf{<>} -- Кроме базовой гарантии, гарантируется, что исключения не возникают. +\item \textbf{<>} -- нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя. + % TODO: No guarantees это не совсем гарантия, это я бы сказал её отсутствие. В корректных программах такое иметься не должно. Я бы даже его отдельно написал. + % FIXME: Зачем про это вообще писать? \end{enumerate} -\subsection{Основные моменты} - -Теперь давайте рассмотрим важные моменты: +Разберем пару примеров для вектора. \begin{itemize} -\item Методы пользовательского интерфейса должны удовлетворять базовым или строгим гарантиям. Это избавляет пользователей от утечек памяти и инвалидных данных. % TODO: А Nothrow guarantee можно? Я бы сказал так, в корректных программах все функции должны иметь какую-то гарантию безопасности исключений либо не использовать исключения. +\item \mintinline{c++}{std::swap} -- имеет гарантию \textit{Nothrow}. То есть при любых обстоятельствах нам гарантируется, что функция отработает корректно. -\item Также важно, чтобы деструктор не пробросал исключений, иначе утечки неизбежны. Например, может возникнуть исключение и при очистки стека, возникает еще одно. % TODO: непонятно +Также этой гарантии отвечает \mintinline{c++}{pop_back()}. -\item Важность гарантии nothrow: -Она есть у очень небольшого количества функции: swap, vector::pop\_back, операции с итераторами, Это гарантия очень важна, так как с ее помощью достигается строгая гарантия, когда мы производим необходимы операции на временном объекте, а потом просто делаем с ним swap. +\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 +vector& vector::operator=(vector const& other) { + return this->swap(vector(other)); // swap trick! +} +\end{minted} +Если конструктор копирования будет отвечать хотя бы базовой гарантии, то оператор копирования можно сделать строгой гарантии. + +\textcolor{red}{NB}) Этот метод, когда мы делаем операции отвечающие базовой гарантии во временном объекте, называется swap trick. + +\item Конструкторы вектора не могу удовлетворить даже строгой, так как до их вызова объект еще не собран и не отвечает инвариантам класса. \end{itemize} \subsection{Best practice} +Методы пользовательского интерфейса должны удовлетворять хотя бы какой-то гарантии. Это избавляет пользователей от утечек памяти и инвалидных данных. + +Деструктор должен отвечать строгой гарантии. То есть не пробрасывать исключения, так как он может быть вызван для очистки стека во время возникновения исключения. В этом случаи мы не может корректно очистить стек. + Пример: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template @@ -59,7 +107,7 @@ \subsection{Best practice} } \end{minted} -Мы копируем other во временный объект vector(other), а потом делаем swap с ним. Если произойдет исключение при копировании other во временный объект, то оно пробросится к нам. swap() не выполнится и исключение проброситься дальше. Наш объект не поменяется. +Мы копируем \mintinline{c++}{other} во временный объект \mintinline{c++}{vector(other)}, а потом делаем \mintinline{c++}{swap} с ним. Если произойдет исключение при копировании \mintinline{c++}{other} во временный объект, то оно пробросится к нам. \mintinline{c++}{swap()} не выполнится и исключение проброситься дальше. Наш объект не поменяется. \textcolor{red}{NB}) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex index d256e8b..406691d 100644 --- a/Exception_and_RAII/Exception.tex +++ b/Exception_and_RAII/Exception.tex @@ -2,7 +2,7 @@ \section{Exceptions} \subsection{Введение} Часто нехватка динамической памяти, неправильный ввод пользователя, ошибка с файловой системой, приводят к тому, что продолжение исполнения логики программы невозможно. Например, если наша функция foo вызывает malloc и malloc вернул ошибку, то функция foo должна завершиться и тоже вернуть ошибку. Возможно, что функция, вызывающая функцию foo, тоже проверит возвращаемое значение и завершится с ошибкой. -В C такая поведение реализовывалось, явной проверкой возвращаемого значения функции с помощю if и исполнением return'а в случае ошибки. C++ имеет встроенный в язык механизм поддержки такого поведения. Этот механизм называется механизмом исключений. +В C такая поведение реализовывалось, явной проверкой возвращаемого значения функции с помощью if и исполнением return'а в случае ошибки. C++ имеет встроенный в язык механизм поддержки такого поведения. Этот механизм называется механизмом исключений. Рассмотрим следующую функцию деления: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -70,7 +70,7 @@ \subsection{Описание конструкций} Catch-блок может иметь две формы: \begin{itemize} \item - \mintinline{c++}{catch(/*declaration*/) { /*обработчик исключения*/ }} Ловит исключение указанного или производных от него типов. Переменной исключения можно дать имя. Это позволяет обращаться к пойманому объекту исключения. + \mintinline{c++}{catch(/*declaration*/) { /*обработчик исключения*/ }} Ловит исключение указанного или производных от него типов. Переменной исключения можно дать имя. Это позволяет обращаться к пойманному объекту исключения. \item \mintinline{c++}{catch(...) { /*обработчик исключения*/ }} Ловит исключения всех типов. В этом случае обращаться к объекту исключения невозможно. @@ -106,7 +106,7 @@ \subsection{Как ловится исключение?} \textcolor{red}{NB}) Так как поиск ведется последовательно, то нужно учитывать порядок catch-блоков (Например, catch(...) должен быть последним). -\textcolor{red}{NB}) Также при наследовании классов исключений следует различать catch(type\& obj) и catch(type obj). В первом случае obj ссылается на этот объект и копии не создается. Во втором случае при входе в catch блок делается копия объекта-исключения, вследствии чего мы теряем возможность вызывать виртуальные функции. +\textcolor{red}{NB}) Также при наследовании классов исключений следует различать catch(type\& obj) и catch(type obj). В первом случае obj ссылается на этот объект и копии не создается. Во втором случае при входе в catch блок делается копия объекта-исключения, вследствие чего мы теряем возможность вызывать виртуальные функции. Пример: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -168,8 +168,10 @@ \subsection{Как ловится исключение?} third(); } catch (Exception_base const &obj) { - std::cout << obj.msg(); - throw; + std::cout << obj.msg() << std::endl; + throw; //пробрасываем + // здесь obj имеет тип Exception_base. + // но это не мешает поймать потом тоже исключение как Exception_derived. } } @@ -178,7 +180,7 @@ \subsection{Как ловится исключение?} second(); } catch (Exception_derived const &obj) { - std::cout << obj.msg(); + std::cout << obj.msg() << std::endl; } } @@ -187,16 +189,16 @@ \subsection{Как ловится исключение?} return 0; } + \end{minted} \textbf{Вывод программы:} \\ -> base \\ > derived \\ -% TODO: я даже специально скомпилировал эту программу. Она выводит derivedderived +> derived \\ Значит тип объекта-параметра в текущем catch-блоке может отличаться от типа исключения, и это не влияет на дальнейшую обработку исключения в других catch-блоках. - Например, это позволяет найти выход из такой ситуации: мы захотели вставить куда-то в глубь уже написанного кода try-catch-блок для логирования всех исключений. Тогда будем ловить по типу Exception\_all, который сделаем предком всех наший исключений. Ловим и пробрасывать дальше, чтобы не нарушать обработку производный от него исключений. + Например, это позволяет найти выход из такой ситуации: мы захотели вставить куда-то в глубь уже написанного кода try-catch-блок для логирования всех исключений. Тогда будем ловить по типу Exception\_all, который сделаем предком всех наших исключений. Ловим и пробрасывать дальше, чтобы не нарушать обработку производный от него исключений. \textcolor{red}{NB})Если необходимо изменить тип исключения, то мы можем в конце catch-блока сразу кинуть новое исключение нового типа. @@ -266,13 +268,15 @@ \subsection{Уничтожение объекта при исключении в Если возникновении исключения в конструкторе некоторого объекта, объект не считается созданным и деструктор для него не вызывается. Если конструктор захватывает некоторые ресурсы и потом бросается исключение, то конструктору следует самостоятельно освободить эти ресурсы перед выбрасыванием исключения. -При исполнении конструктора класса, вызываются конструкторы всех членов этого класса. Если исключение возникает при создании одного из членов класса, то в процессе раскрутки стека будут вызваны деструкторы от всех уже созданных членов. У самого объекта деструктор не вызвается, так как объект не считается созданным пока его конструктор не отработал полностью. +При исполнении конструктора класса, вызываются конструкторы всех членов этого класса. Если исключение возникает при создании одного из членов класса, то в процессе раскрутки стека будут вызваны деструкторы от всех уже созданных членов. У самого объекта деструктор не вызывается, так как объект не считается созданным пока его конструктор не отработал полностью. \subsection{Best practice} % TODO: не должно ли это относится к exception-safety? -% TODO: первая фраза неправильная: не "исключения применяются для корректной работы с ресурсами", более правильно будо бы сказать -% что-то вроде "корректная работа с ресурсами должна учитывать, то это существуют исключения". -Часто исключения применяются для корректной работы с ресурсами. То есть если возникает исключение и мы владеем какими-то ресурсам, то в случае генерации исключения следует их освободить. +% FIXME может просто написать это здесь и кинуть ссылку + +\begin{itemize} +\item +Для корректной работы с ресурсами необходимо учитывать существование исключений. То есть, если возникает исключение и некоторые из ресурсов еще не были освобождены, то следует поймать исключение и освободить оставшиеся ресурсы. Например, мы пишем конструктор копирования для вектора, и нам необходимо скопировать данные в другой участок памяти. При этом если во время копирование какого-то объекта возникнет исключение, то хорошо если уже созданные объекты будут разрушены. Причем желательно в обратном порядке их создания. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -293,14 +297,17 @@ \subsection{Best practice} } \end{minted} -Полезно знать про стандартные исключения, такие как \mintinline{c++}{std::bad_alloc}, \mintinline{c++}{std::bad_cast}, \mintinline{c++}{std::bad_typeid} и т. д. Они также связанны наследованием и имеют общего предка \mintinline{c++}{std::exception}. +\item +Полезно знать про стандартные исключения, такие как \mintinline{c++}{std::bad_alloc}, \mintinline{c++}{std::bad_cast}, \mintinline{c++}{std::bad_typeid} и т. д. Они связанны иерархией наследования и имеют общего предка \mintinline{c++}{std::exception}. Подробнее можно почитать здесь: \\ \url{https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm} \\ \url{http://en.cppreference.com/w/cpp/error/exception} \\ +\item Хорошим тоном является наследование от \mintinline{c++}{std::exception}. +\item Когда мы организовываем исключения в иерархии классов, то получаем мощный механизм описания исключение и способов их обработки. Создав такую структуры мы можем обрабатывать как более общие ошибки, так и более специализированные. % TODO: согласен, можно даже упомянуть про использование виртуального наследования для исключений Пример: @@ -309,14 +316,15 @@ \subsection{Best practice} class popOnEmpty(): public StackException {}; class pushOnFull(): public StackException {}; \end{minted} - Причем сгенерировав исключение типа popOnEmpty(), мы можем в разных обработчиках независимо выбирать: обработать как popOnEmpty или как StackException, так как тип исключения не теряется при повторной генерации этого исключения. + Причем сгенерировав исключение типа \mintinline{c++}{popOnEmpty}, мы можем в разных обработчиках независимо выбирать: обработать как \mintinline{c++}{popOnEmpty} или как \mintinline{c++}{StackException}, так как тип исключения не теряется при повторной генерации этого исключения. +\end{itemize} \subsection{Bad practice} \begin{itemize} \item -Плохо писать код бросающий исключения в catch-блоке. % TODO: почему? +Плохо писать код бросающий исключения в catch-блоке, так как тогда возникнет ситуация, когда сущетсвует два исключения. Два исключения не могут обрабатываться одновременно, поэтому будет вызвана фукнция \mintinline{c++}{std::terminate()} \item -Хотя если исключений не происходит, то по скорости выполнения программа не сильно упадет, но иногда нужно учитывать большой overhead в случае возникновения и обработки исключения. % TODO: не надо не так писать. Надо сказать: "код использующий исключения такой же быстрый как код без исключений, если исключения не возникают, но если они возникают то в современных реализациях они как правило дороже проверок на коды возврата, поэтому не рекомендуется использовать исключения как часть обычного control-flow". +Код использующий исключения такой же быстрый как и код без них, если исключения не возникают. Если же исключения возникают, то в современных реализациях они обычно дороже проверок на коды возврата, поэтому не рекомендуется использовать исключения как часть обычного control-flow. \end{itemize} \subsection{std::terminate()} @@ -331,10 +339,11 @@ \subsection{std::terminate()} \item При подобных и не только ошибках в потоках. % TODO: при подобных при каких? очень странная фраза, если хочется сказать, что то, что из главной функции потока нельзя выбрасывать, то её можно даже объединить с пунктом про main. \end{itemize} -По умолчанию \mintinline{c++}{std::terminate()} вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистрировать ее как терминальную. % TODO: Пример интересный, но ошибочный. my_terminate не может возвращаться. Она должна завершить исполнение программы. +По умолчанию \mintinline{c++}{std::terminate()} вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистрировать ее как терминальную. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void my_terminate() { cout << "It's not a bug, it's a feature!"; + std::abort(); } /**/ set_terminate(my_teminate); diff --git a/Exception_and_RAII/RAII.tex b/Exception_and_RAII/RAII.tex index df6d505..9ff08f8 100644 --- a/Exception_and_RAII/RAII.tex +++ b/Exception_and_RAII/RAII.tex @@ -10,9 +10,8 @@ \subsection{Пример} } \end{minted} - -Причем гарантировать их закрытие в случает возникновения исключения. -Тогда давайте напишем такой код: +Но такой код не гарантирует закрытие файлов в случае возникновения исключения. +Тогда давайте вставим проверку исключений: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void read_some_file() { try { @@ -20,7 +19,7 @@ \subsection{Пример} open_for_read("second_part.txt"); open_for_read("third_part.txt"); } catch (...) { - // Но мы не может понять какой файл открылся, какой нет. :( + // Но мы не может понять какой файл открылся, а какой нет. :( } } \end{minted} @@ -55,7 +54,7 @@ \subsection{Пример} } \end{minted} -Этот получился довольно простой и безопасный. Почему безопасный? Потому, что объект reader открывает файл в конструкторе и закрывает в деструкторе. И если возникнет исключение, то при раскрутке стека удаляться все локальные объекты, в том числе и файловые дескрипторы, закрывая файлы. Если же какой-то файл не успел открыться, то не успел и создаться объект отвечающий за него. +Этот код получился довольно простой и безопасный. Почему безопасный? Потому, что объект \mintinline{c++}{Reader} открывает файл в конструкторе и закрывает в деструкторе. И если возникнет исключение, то при раскрутке стека удаляться все локальные объекты, в том числе и файловые дескрипторы, закрывая файлы. Если же какой-то файл не успел открыться, то не успел и создаться объект отвечающий за него. Это удобная идея получила название: RAII. @@ -64,7 +63,7 @@ \subsection{Определение} \textbf{<>} или <<Захват ресурса - это инициализация>> - это идиома класса, который инкапсулирует управление каким-то ресурсом. Она значит, что объект этого класса, получает доступ к ресурсу и удерживает его в течении своей жизни, а потом этот ресурс высвобождается при уничтожении объекта. В конструкторе он должен захватить ресурс(открыть файл, выделить файл и т. д.), а в десткрукторе освободить его(закрыть файл, освободить память и т. д.). -Также важно подумать, что должно происходить при копировании объекта, часто мы просто явно запрещаем это делать. +Также важно подумать, что должно происходить при копировании объекта, часто мы явно запрещаем это делать. Так как иначе ресурс может освободиться два раза. \subsection{Что это дает?} \begin{itemize} @@ -99,8 +98,8 @@ \subsection{Что это дает?} RAII часто встречается стандартной библиотеке: \begin{itemize} \item Smart pointers -- инкапсулируют несколько видов управления памятью -\item i/ofstream -\item Различные контейнеры +\item i/ofstream -- инкапсулиет оправление файлом +\item Различные контейнеры stl -- инкапсулируют управление памятью, для хранение объектов. \end{itemize} \subsection{Best practice} diff --git a/how-to-contribute.tex b/how-to-contribute.tex index e3fd844..e2290bf 100644 --- a/how-to-contribute.tex +++ b/how-to-contribute.tex @@ -58,8 +58,12 @@ \subsection{Linux} \textcolor{red}{NB}) \href{http://tug.ctan.org/macros/latex/contrib/minted/minted.pdf}{Документация для minted} -\subsection{Win} - TODO +\subsection{Windows} +\begin{enumerate} +\item Скачать Tex Studio +\item TODO +\end{enumerate} +\subsection{Mac OS} \section{Работа с Git} Для того, чтобы работать над конспектом необходимо использовать git. Поэтому я привел несколько ситуации, в которые вы неизбежно попадете. @@ -102,8 +106,7 @@ \section{Работа с Git} \textcolor{red}{NB}) Если кто не силен в ветвлении, смотрите все туже \href{https://git-scm.com/book/ru/v1}{книгу}. \section{LaTeX} - TODO - + Обычно все довольно не плохо гуглиться) \subsection{Структура} \begin{enumerate} From f038d93279137f16fa30095ca0e3a96efc582364 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sat, 30 Dec 2017 14:17:00 +0300 Subject: [PATCH 27/30] another example --- Exception_and_RAII/Exception-safety.tex | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Exception_and_RAII/Exception-safety.tex b/Exception_and_RAII/Exception-safety.tex index d30402d..2458df1 100644 --- a/Exception_and_RAII/Exception-safety.tex +++ b/Exception_and_RAII/Exception-safety.tex @@ -11,6 +11,39 @@ \subsection{Мотивирующий пример} } \end{minted} +TODO: пример +string& operator=(string const& rhs) +{ + size_ = rhs.size_; + delete[] data_; + data_ = new char[rhs.size_ + 1]; + memcpy(data_, rhs.data_, rhs.size_ + 1); + + return *this; +} + +string& operator=(string const& rhs) +{ + size_ = rhs.size_; + char* tmp = new char[rhs.size_ + 1]; + delete[] data_; + data_ = tmp; + memcpy(data_, rhs.data_, rhs.size_ + 1); + + return *this; +} + +string& operator=(string const& rhs) +{ + char* tmp = new char[rhs.size_ + 1]; + delete[] data_; + data_ = tmp; + memcpy(data_, rhs.data_, rhs.size_ + 1); + size_ = rhs.size_; + + return *this; +} + На первый взгляд это довольно безобидный код. Но он совершено не учитывает возможность возникновения исключения в одном из вызовов конструктора \mintinline{c++}{T()}. Что произойдет в этом случаи? Исключение пролетит дальше. Предположим, что его кто-то поймал. Но тогда он обнаружит наш вектор в сломанном состоянии. И не сможет ничего с ним сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушены инварианты класса. From ace4cba785c408005364543e18ca1c2e7b474f91 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sat, 30 Dec 2017 14:41:39 +0300 Subject: [PATCH 28/30] a snippet --- Exception_and_RAII/RAII.tex | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/Exception_and_RAII/RAII.tex b/Exception_and_RAII/RAII.tex index 9ff08f8..467f1a5 100644 --- a/Exception_and_RAII/RAII.tex +++ b/Exception_and_RAII/RAII.tex @@ -2,6 +2,102 @@ \section{RAII-classes} \subsection{Пример} Начнем с примера. Пусть мы хотим открыть несколько файлов. +% TODO: примеры +bool process_files() +{ + FILE* f1 = fopen("foo"); + if (!f1) + return false; + + FILE* f2 = fopen("bar"); + if (!f2) + return false; + + FILE* f3 = fopen("baz"); + if (!f3) + return false; + + fclose(f3); + fclose(f2); + fclose(f1); + return true; +} + + +FILE* my_fopen(char const* filename) +{ + FILE* result = fopen(filename); + if (!result) + throw std::runtime_error("fopen failed"); + + return result; +} + +void process_files() +{ + FILE* f1 = fopen("foo"); + FILE* f2 = fopen("bar"); + FILE* f3 = fopen("baz"); + + fclose(f3); + fclose(f2); + fclose(f1); +} + +void process_files() +{ + FILE* f1 = fopen("foo"); + try + { + FILE* f2 = fopen("bar"); + try + { + FILE* f3 = fopen("baz"); + + try + { + // ... + } + catch (...) + { + fclose(f3); + throw; + } + fclose(f3); + } + catch (...) + { + fclose(f2); + throw; + } + fclose(f2); + } + catch (...) + { + fclose(f1); + throw; + } + fclose(f1); +} + +void process_files() +{ + X* x = new X; + Y* y = new Y; + Z* z = new Z; + + delete z; + delete y; + delete x; +} + +void process_files() +{ + file f1("foo"); + file f2("bar"); + file f3("baz"); +} + \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void read_some_file() { open_for_read("first_part.txt"); From ca9cc158dbd3b05f2525577efe2513005e9d4bf8 Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Sat, 6 Jan 2018 01:33:10 +0300 Subject: [PATCH 29/30] fixed last TODO in Except and Except-safety --- Exception_and_RAII/Exception-safety.tex | 136 +++++++++++++----------- Exception_and_RAII/Exception.tex | 40 +++++-- Exception_and_RAII/RAII.tex | 2 + 3 files changed, 103 insertions(+), 75 deletions(-) diff --git a/Exception_and_RAII/Exception-safety.tex b/Exception_and_RAII/Exception-safety.tex index d30402d..920c0a6 100644 --- a/Exception_and_RAII/Exception-safety.tex +++ b/Exception_and_RAII/Exception-safety.tex @@ -1,61 +1,37 @@ \section{Exception safety} \subsection{Мотивирующий пример} -Рассмотрим функцию копирования данных в векторе. +Рассмотрим оператор копирования для \mintinline{c++}{string}. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void vector::operator=(vector const& other) { - data = new char(sizeof(T) * other.size()); - for (size_t i = 0; i != other.size(); ++i) { - new (data + i) T(other[i]); - } +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} +\end{minted} -На первый взгляд это довольно безобидный код. Но он совершено не учитывает возможность возникновения исключения в одном из вызовов конструктора \mintinline{c++}{T()}. Что произойдет в этом случаи? +На первый взгляд это довольно безобидный код. Но он совершено не учитывает возможность возникновения исключения при выделении памяти. Что произойдет в этом случаи? -Исключение пролетит дальше. Предположим, что его кто-то поймал. Но тогда он обнаружит наш вектор в сломанном состоянии. И не сможет ничего с ним сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушены инварианты класса. +Мы не ловим исключение, поэтому оно пролетит дальше. Предположим, что его кто-то поймал. Тогда он обнаружит, что наша строка в сломанном состоянии и он не может ничего с ней сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушились инварианты класса: data указывает на удаленную память, а size при этом имеет не нулевое значени. -Напишем аккуратнее. +Напишем аккуратнее: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -void vector::operator=(vector const& other) { - size_t i = 0; - try { - data = new char[sizeof(T) * other.size()]; - for (size_t i = 0; i != other.size(); ++i) { - new (data + i) T(other[i]); - } - } - catch(...) { // если ошибка при копировании - for (size_t j = i; j != 0; --j) { - data[j - 1].~T(); // вызовем деструкторы созданных объектов - } - delete[] data; - data = nullptr; - } +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} -Если исключения не возникнет то функция скопирует данные, иначе data занулится, что будет соответствовать пустому вектору. И мы сохранили инварианты, то есть класс остался в корректном состоянии и пользователь может вызывать public-методы. - -Например: -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -vector a, b; -/* -* some code -*/ -try { - a = b -} catch(...) { - if (a.size() == 0) { - std::cout << "Bad copy" << std::endl; - } -} -\end{minted} +Если исключения не возникнет, то функция скопирует данные, иначе объект останется не изменным. Это позволяет пользователю класса корректно обрабатывать ошибки, используя public-методы класса, так как инварианты выполняются. Также важно, что теперь при возникновнии исключения данные не теряются. Давайте обобщим этот пример: -Часто мы хотим, что в случае возникновения исключение в коде какой-то структуры данных инварианты класса сохранялись, чтобы пользователь мог корректно обработать вылетевшие исключения. +Часто мы хотим, чтобы в случае возникновения исключение в коде какой-то структуры данных инварианты класса сохранились, чтобы пользователь мог корректно обработать вылетевшие исключения. Также желательно, чтобы состояние объекта не изменилось. Это идея развилась в Гарантии безопасности исключений. @@ -69,50 +45,82 @@ \subsection{Определение} \item \textbf{<>} -- Включает в себя базовую гарантию. А также требует, что в случае исключения объект остается в том, состоянии, в котором он был до выполнения операции. То есть либо операция прошла успешно, или она не повлияла на объект. \item \textbf{<>} -- Кроме базовой гарантии, гарантируется, что исключения не возникают. \item \textbf{<>} -- нет ни каких гарантий. После выполнения метода объект и данные в нем могут быть в любом состоянии. Предполагается, что продолжать работу программы нельзя. - % TODO: No guarantees это не совсем гарантия, это я бы сказал её отсутствие. В корректных программах такое иметься не должно. Я бы даже его отдельно написал. - % FIXME: Зачем про это вообще писать? \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} не измениться. +\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 vector& vector::operator=(vector const& other) { return this->swap(vector(other)); // swap trick! } \end{minted} -Если конструктор копирования будет отвечать хотя бы базовой гарантии, то оператор копирования можно сделать строгой гарантии. +Такой оператор копирования будет отвечать строгой гарантии, если \mintinline{c++}{std::swap} не будет бросать и конструктор будет отвечать хотя бы строгой гарантии. + +\textcolor{red}{NB}) Этот метод называется swap trick. Его суть заключается в том, что мы делаем операции отвечающие хотя бы базовой гарантии во временном объекте. После чего заменяем им наш текущий объект. Если исключение произойдет до замены, то изначальное состояние объекта не потеряется. -\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} +Тут может быть несколько проблем. Во-первых делать копию состояния может быть дорого. Во-вторых это может быть просто не возможно. Например, если изменилось состояние базы данных, то откатить его нет возможности. -\item Конструкторы вектора не могу удовлетворить даже строгой, так как до их вызова объект еще не собран и не отвечает инвариантам класса. +Поэтому иногда предоставить базовую гарантию -- лучшее решение. \end{itemize} \subsection{Best practice} +\begin{itemize} -Методы пользовательского интерфейса должны удовлетворять хотя бы какой-то гарантии. Это избавляет пользователей от утечек памяти и инвалидных данных. +\item +Старайтесь предоставлять самую сильную гарантию, где это оправдано. +Как минимум, методы пользовательского интерфейса должны удовлетворять хотя бы какой-то гарантии. Это избавляет пользователей от утечек памяти и инвалидных данных. -Деструктор должен отвечать строгой гарантии. То есть не пробрасывать исключения, так как он может быть вызван для очистки стека во время возникновения исключения. В этом случаи мы не может корректно очистить стек. +\item + По возмножности деструкторы должны отвечать гарантии Nothrow, так как они могут быть вызваны по время раскутки стека. И если в это время произодет исключение, то программа будет завершена функцией \mintinline{c++}{std::terminate()}. -Пример: -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -template -vector& vector::operator=(vector const& other) { - return this->swap(vector(other)); // swap trick! -} -\end{minted} + Начиная с c++11 все деструкторы неявно помечены как \mintinline{c++}{noexcept}. + +\item Используйте swap trick. -Мы копируем \mintinline{c++}{other} во временный объект \mintinline{c++}{vector(other)}, а потом делаем \mintinline{c++}{swap} с ним. Если произойдет исключение при копировании \mintinline{c++}{other} во временный объект, то оно пробросится к нам. \mintinline{c++}{swap()} не выполнится и исключение проброситься дальше. Наш объект не поменяется. +\item Спецификатор \mintinline{c++}{noexcept} (C++11) указывает компилятору, что выполняется гарантия nothrow. -\textcolor{red}{NB}) Спецификатор noexcept (C++11) указывает компилятору, что выполняется гарантия nothrow. Это важно, для выбора конструктора копирования: перемещающего или нет, так как при перемещении бывает сложно обработать исключение. +Это важная информация для компилятора, которая позволят делать некоторые оптимизации кода связанные с проверкой исключений. -Главным способом предотвращением утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). +Также это может быть важно при использовании STL. Так как при перемещении бывает сложно обратывать исключения, то стандартные алгоритмы могут игнорировать ваш конструктор перемещения если он не помечен как \mintinline{c++}{noexcept}. -\textcolor{red}{Offtops:} +\item +Главным способом предотвращения утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). + +\end{itemize} +\textcolor{red}{Offtop:} Можно попросить оператор \mintinline{c++}{new} не кидать исключение с помощью константы \mintinline{c++}{std::nothrow} diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex index 406691d..423603b 100644 --- a/Exception_and_RAII/Exception.tex +++ b/Exception_and_RAII/Exception.tex @@ -271,14 +271,11 @@ \subsection{Уничтожение объекта при исключении в При исполнении конструктора класса, вызываются конструкторы всех членов этого класса. Если исключение возникает при создании одного из членов класса, то в процессе раскрутки стека будут вызваны деструкторы от всех уже созданных членов. У самого объекта деструктор не вызывается, так как объект не считается созданным пока его конструктор не отработал полностью. \subsection{Best practice} -% TODO: не должно ли это относится к exception-safety? -% FIXME может просто написать это здесь и кинуть ссылку - \begin{itemize} \item Для корректной работы с ресурсами необходимо учитывать существование исключений. То есть, если возникает исключение и некоторые из ресурсов еще не были освобождены, то следует поймать исключение и освободить оставшиеся ресурсы. -Например, мы пишем конструктор копирования для вектора, и нам необходимо скопировать данные в другой участок памяти. При этом если во время копирование какого-то объекта возникнет исключение, то хорошо если уже созданные объекты будут разрушены. Причем желательно в обратном порядке их создания. +Например, мы пишем конструктор копирования для вектора, и нам необходимо скопировать данные в другой участок памяти. При этом если во время копирование какого-то объекта возникнет исключение, то обязательно уже созданные объекты должны быть разрушены. Причем иногда важно делать это в обратном порядке их создания. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} template void copy_construct(T* dist, T const * sourse, size_t size) { @@ -309,7 +306,7 @@ \subsection{Best practice} \item Когда мы организовываем исключения в иерархии классов, то получаем мощный механизм описания исключение и способов их обработки. Создав такую структуры мы можем обрабатывать как более общие ошибки, так и более специализированные. -% TODO: согласен, можно даже упомянуть про использование виртуального наследования для исключений + Пример: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} class StackException {}; @@ -318,12 +315,33 @@ \subsection{Best practice} \end{minted} Причем сгенерировав исключение типа \mintinline{c++}{popOnEmpty}, мы можем в разных обработчиках независимо выбирать: обработать как \mintinline{c++}{popOnEmpty} или как \mintinline{c++}{StackException}, так как тип исключения не теряется при повторной генерации этого исключения. +\item Виртуальное наследование классов исключений: + +Рассмотрим иерархию исключений для кода, которому необходимо обрабатывать ошибки ввода. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + class InterfaceException {}; + class InputException(): public virtual InterfaceException {}; + class OutputException(): public virtual InterfaceException {}; + class InputOrOutputException: public InputException, public OutputException {}; +\end{minted} + Важно что мы наследуемся от \mintinline{c++}{InterfaceException} вирутально, так как иначе \mintinline{c++}{catch(InterfaceException const&)} не будет ловить \mintinline{c++}{InputOrOutputException}. Это связано с тем, что путь наследования без виртуального наследования будет не опреден. + +\item +Если необходимо бросать и ловить исключения из деструктора, то нужно помнить, что начиная с C++11, все деструкторы неявно помечены как \mintinline{c++}{noexcept}. Из-за этого необходимо явно указать, что дектруктор может бросать. Если этого не сделать, то при попытке выбросить исключение из деструктора вызовется функция \mintinline{c++}{std::terminate()}. + +Пример: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} + class A { + ~A() noexcept(false) { + //... + } + }; +\end{minted} + \end{itemize} \subsection{Bad practice} \begin{itemize} \item -Плохо писать код бросающий исключения в catch-блоке, так как тогда возникнет ситуация, когда сущетсвует два исключения. Два исключения не могут обрабатываться одновременно, поэтому будет вызвана фукнция \mintinline{c++}{std::terminate()} -\item Код использующий исключения такой же быстрый как и код без них, если исключения не возникают. Если же исключения возникают, то в современных реализациях они обычно дороже проверок на коды возврата, поэтому не рекомендуется использовать исключения как часть обычного control-flow. \end{itemize} @@ -332,14 +350,14 @@ \subsection{std::terminate()} Это функция, которая вызывается если у механизма исключений не получается корректно отработать, чтобы завершить программу. Случаи когда она вызывается: \begin{itemize} -\item Исключение брошено и не поймано ни одним catch-блоком, то есть пробрасывается вне main(). -\item Исключение бросается во время обработки другого исключения. Это может произойти только в catch-блоке или деструкторе. А также в функциях, которые вызываются ими. % TODO: А почему могут какие-то проблемы в catch-блоке возникнуть? +\item Если исключение брошено и не поймано ни одним catch-блоком, то есть пробрасывается вне main(). +\item Если во время обработки исключения десктруктор, вызванный при раскрутке стека, бросает исключение и оно вылетает наружу. \item Если функция переданная в \mintinline{c++}{std::atexit} и \mintinline{c++}{std::at_quick_exit} бросит исключение. \item Если функция нарушит гарантии noexcept specification. Например, если функция помеченная как noexcept бросит исключение. -\item При подобных и не только ошибках в потоках. % TODO: при подобных при каких? очень странная фраза, если хочется сказать, что то, что из главной функции потока нельзя выбрасывать, то её можно даже объединить с пунктом про main. +\item Если конструктор или деструктор статического или локального для одного треда объект бросает исключение. \end{itemize} -По умолчанию \mintinline{c++}{std::terminate()} вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистрировать ее как терминальную. +По умолчанию \mintinline{c++}{std::terminate()} просто вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистриров ее как терминальную. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void my_terminate() { cout << "It's not a bug, it's a feature!"; diff --git a/Exception_and_RAII/RAII.tex b/Exception_and_RAII/RAII.tex index 9ff08f8..1eab909 100644 --- a/Exception_and_RAII/RAII.tex +++ b/Exception_and_RAII/RAII.tex @@ -10,6 +10,8 @@ \subsection{Пример} } \end{minted} +%TODO переделать пример. + Но такой код не гарантирует закрытие файлов в случае возникновения исключения. Тогда давайте вставим проверку исключений: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} From ac117203aa2b601d9a8704ec8010e3025fec9a7e Mon Sep 17 00:00:00 2001 From: Golovin Pavel Date: Sat, 6 Jan 2018 03:14:41 +0300 Subject: [PATCH 30/30] Fixed TODO 3 (RAII) and typo --- Exception_and_RAII/Exception-safety.tex | 10 +- Exception_and_RAII/Exception.tex | 4 +- Exception_and_RAII/RAII.tex | 228 ++++++++---------------- 3 files changed, 79 insertions(+), 163 deletions(-) diff --git a/Exception_and_RAII/Exception-safety.tex b/Exception_and_RAII/Exception-safety.tex index 6b65ce2..43e219a 100644 --- a/Exception_and_RAII/Exception-safety.tex +++ b/Exception_and_RAII/Exception-safety.tex @@ -13,7 +13,7 @@ \subsection{Мотивирующий пример} На первый взгляд это довольно безобидный код. Но он совершено не учитывает возможность возникновения исключения при выделении памяти. Что произойдет в этом случаи? -Мы не ловим исключение, поэтому оно пролетит дальше. Предположим, что его кто-то поймал. Тогда он обнаружит, что наша строка в сломанном состоянии и он не может ничего с ней сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушились инварианты класса: data указывает на удаленную память, а size при этом имеет не нулевое значени. +Мы не ловим исключение, поэтому оно пролетит дальше. Предположим, что его кто-то поймал. Тогда он обнаружит, что наша строка в сломанном состоянии и он не может ничего с ней сделать. Любой вызов public-метода вызывает undefined behavior, так как нарушились инварианты класса: data указывает на удаленную память, а size при этом имеет не нулевое значение. Напишем аккуратнее: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} @@ -27,7 +27,7 @@ \subsection{Мотивирующий пример} } \end{minted} -Если исключения не возникнет, то функция скопирует данные, иначе объект останется не изменным. Это позволяет пользователю класса корректно обрабатывать ошибки, используя public-методы класса, так как инварианты выполняются. Также важно, что теперь при возникновнии исключения данные не теряются. +Если исключения не возникнет, то функция скопирует данные, иначе объект останется неизменным. Это позволяет пользователю класса корректно обрабатывать ошибки, используя public-методы класса, так как инварианты выполняются. Также важно, что теперь при возникновении исключения данные не теряются. Давайте обобщим этот пример: @@ -80,7 +80,7 @@ \subsection{Определение} f(); } \end{minted} -Необходимо предоставить для функции \mintinline{c++}{g()} строгую гарантию. Если фукнция \mintinline{c++}{f()} отвечает только базовой гарантии, то необходимо запомнить перед ее вызовом состояние программы. Не всегда достаточно запомнить только копию объекта. Если \mintinline{c++}{f()} именяет глобальные данные, то придется запомнить и их состояние. +Необходимо предоставить для функции \mintinline{c++}{g()} строгую гарантию. Если функция \mintinline{c++}{f()} отвечает только базовой гарантии, то необходимо запомнить перед ее вызовом состояние программы. Не всегда достаточно запомнить только копию объекта. Если \mintinline{c++}{f()} именяет глобальные данные, то придется запомнить и их состояние. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void g() { // делаем копию данных @@ -105,7 +105,7 @@ \subsection{Best practice} Как минимум, методы пользовательского интерфейса должны удовлетворять хотя бы какой-то гарантии. Это избавляет пользователей от утечек памяти и инвалидных данных. \item - По возмножности деструкторы должны отвечать гарантии Nothrow, так как они могут быть вызваны по время раскутки стека. И если в это время произодет исключение, то программа будет завершена функцией \mintinline{c++}{std::terminate()}. + По возможности деструкторы должны отвечать гарантии Nothrow, так как они могут быть вызваны по время раскрутки стека. И если в это время произойдет исключение, то программа будет завершена функцией \mintinline{c++}{std::terminate()}. Начиная с c++11 все деструкторы неявно помечены как \mintinline{c++}{noexcept}. @@ -115,7 +115,7 @@ \subsection{Best practice} Это важная информация для компилятора, которая позволят делать некоторые оптимизации кода связанные с проверкой исключений. -Также это может быть важно при использовании STL. Так как при перемещении бывает сложно обратывать исключения, то стандартные алгоритмы могут игнорировать ваш конструктор перемещения если он не помечен как \mintinline{c++}{noexcept}. +Также это может быть важно при использовании STL. Так как при перемещении бывает сложно обрабатывать исключения, то стандартные алгоритмы могут игнорировать ваш конструктор перемещения если он не помечен как \mintinline{c++}{noexcept}. \item Главным способом предотвращения утечек памяти и других ресурсов является идиома RAII-классов (об этом подробнее ниже). diff --git a/Exception_and_RAII/Exception.tex b/Exception_and_RAII/Exception.tex index 423603b..a17b73d 100644 --- a/Exception_and_RAII/Exception.tex +++ b/Exception_and_RAII/Exception.tex @@ -324,7 +324,7 @@ \subsection{Best practice} class OutputException(): public virtual InterfaceException {}; class InputOrOutputException: public InputException, public OutputException {}; \end{minted} - Важно что мы наследуемся от \mintinline{c++}{InterfaceException} вирутально, так как иначе \mintinline{c++}{catch(InterfaceException const&)} не будет ловить \mintinline{c++}{InputOrOutputException}. Это связано с тем, что путь наследования без виртуального наследования будет не опреден. + Важно что мы наследуемся от \mintinline{c++}{InterfaceException} виртуально, так как иначе \mintinline{c++}{catch(InterfaceException const&)} не будет ловить \mintinline{c++}{InputOrOutputException}. Это связано с тем, что путь наследования без виртуального наследования будет не опреден. \item Если необходимо бросать и ловить исключения из деструктора, то нужно помнить, что начиная с C++11, все деструкторы неявно помечены как \mintinline{c++}{noexcept}. Из-за этого необходимо явно указать, что дектруктор может бросать. Если этого не сделать, то при попытке выбросить исключение из деструктора вызовется функция \mintinline{c++}{std::terminate()}. @@ -357,7 +357,7 @@ \subsection{std::terminate()} \item Если конструктор или деструктор статического или локального для одного треда объект бросает исключение. \end{itemize} -По умолчанию \mintinline{c++}{std::terminate()} просто вызвает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистриров ее как терминальную. +По умолчанию \mintinline{c++}{std::terminate()} просто вызывает \mintinline{c++}{std::abort()}, но можно это изменить, написав свою функцию \mintinline{c++}{my_terminate()} и зарегистриров ее как терминальную. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} void my_terminate() { cout << "It's not a bug, it's a feature!"; diff --git a/Exception_and_RAII/RAII.tex b/Exception_and_RAII/RAII.tex index 66b55fb..2a9fb50 100644 --- a/Exception_and_RAII/RAII.tex +++ b/Exception_and_RAII/RAII.tex @@ -1,40 +1,20 @@ \section{RAII-classes} \subsection{Пример} -Начнем с примера. Пусть мы хотим открыть несколько файлов. +Начнем с примера. -% TODO: примеры -bool process_files() -{ - FILE* f1 = fopen("foo"); - if (!f1) - return false; - - FILE* f2 = fopen("bar"); - if (!f2) - return false; - - FILE* f3 = fopen("baz"); - if (!f3) - return false; - - fclose(f3); - fclose(f2); - fclose(f1); - return true; -} - - -FILE* my_fopen(char const* filename) -{ - FILE* result = fopen(filename); +У нас есть функция \mintinline{c++}{my_fopen()} для открытия файлов на чтение, которая в случаи ошибки открытия файла кидает исключение. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +FILE* my_fopen(char const* filename) { + FILE* result = fopen(filename, "r"); if (!result) throw std::runtime_error("fopen failed"); return result; } - -void process_files() -{ +\end{minted} +Пусть необходимо в функции \mintinline{c++}{process_files()} открыть несколько файлов. +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void process_files() { FILE* f1 = fopen("foo"); FILE* f2 = fopen("bar"); FILE* f3 = fopen("baz"); @@ -43,118 +23,73 @@ \subsection{Пример} fclose(f2); fclose(f1); } - -void process_files() -{ +\end{minted} +Но такой код не гарантирует закрытие файлов в случае возникновения исключения. +Тогда давайте вставим проверку исключений: +\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} +void process_files() { FILE* f1 = fopen("foo"); - try - { + try { FILE* f2 = fopen("bar"); - try - { + try { FILE* f3 = fopen("baz"); - - try - { - // ... + try { + /* ... */ } - catch (...) - { + catch(...) { fclose(f3); throw; } fclose(f3); } - catch (...) - { + catch(...) { fclose(f2); throw; } fclose(f2); } - catch (...) - { + catch(...) { fclose(f1); throw; } fclose(f1); } - -void process_files() -{ - X* x = new X; - Y* y = new Y; - Z* z = new Z; - - delete z; - delete y; - delete x; -} - -void process_files() -{ - file f1("foo"); - file f2("bar"); - file f3("baz"); -} - -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -void read_some_file() { - open_for_read("first_part.txt"); - open_for_read("second_part.txt"); - open_for_read("third_part.txt"); -} \end{minted} +Итого код увеличился в несколько раз. -%TODO переделать пример. -Но такой код не гарантирует закрытие файлов в случае возникновения исключения. -Тогда давайте вставим проверку исключений: +Однако, с помощью RAII-класса файловых дескрипторов \mintinline{c++}{Reader}, можно упростить код: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -void read_some_file() { - try { - open_for_read("first_part.txt"); - open_for_read("second_part.txt"); - open_for_read("third_part.txt"); - } catch (...) { - // Но мы не может понять какой файл открылся, а какой нет. :( - } +void process_files() { + Reader f1("foo"); + Reader f2("bar"); + Reader f3("baz"); } \end{minted} -Так как мы хотим запоминать какие файлы успели открыться, то перепишем так: +Этот код получился довольно простой и безопасный. Почему безопасный? Потому, что объект \mintinline{c++}{Reader} открывает файл в конструкторе и закрывает в деструкторе. И если возникнет исключение, то при раскрутке стека удаляться все локальные объекты, в том числе и файловые дескрипторы, закрывая файлы. Если же какой-то файл не успел открыться, то не успел и создаться объект отвечающий за него. +Реализация \mintinline{c++}{Reader} могла бы выглядеть так: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -void read_some_file() { - size_t i = 0; - string files[3] = {"first_part.txt", "second_part.txt", "third_part.txt" }; - try { - for (; i < 3; ++i) { - open_for_read(files[i]); - } - } catch (...) { - for (size_t j = i; j != 0; --j) { - close(files[j - 1]); - } - } -} -\end{minted} +class Reader { +public: + Reader(char const *filename): + _file(fopen(filename, "r")) { } //захват ресурса -Итого код увеличился в два раза. + ~Reader() { + fclose(_file); // освобождение ресурса + } -С помощью RAII-класса файловых дескрипторов Reader, можно упростить код: + Reader(Reader const &) = delete; + Reader operator=(Reader const &) = delete; -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -void read_some_file() { - Reader reader1("first_part.txt"); - Reader reader2("second_part.txt"); - Reader reader3("third_part.txt"); -} +private: + FILE *_file; +}; \end{minted} +Важно, что мы запретили копирование объектов класса, так как за один файл должен отвечать один объект. Без этого файл мог бы два раза закрыться. -Этот код получился довольно простой и безопасный. Почему безопасный? Потому, что объект \mintinline{c++}{Reader} открывает файл в конструкторе и закрывает в деструкторе. И если возникнет исключение, то при раскрутке стека удаляться все локальные объекты, в том числе и файловые дескрипторы, закрывая файлы. Если же какой-то файл не успел открыться, то не успел и создаться объект отвечающий за него. - -Это удобная идея получила название: RAII. +Это удобная идея получила название <>. \subsection{Определение} @@ -163,7 +98,7 @@ \subsection{Определение} Также важно подумать, что должно происходить при копировании объекта, часто мы явно запрещаем это делать. Так как иначе ресурс может освободиться два раза. -\subsection{Что это дает?} +\subsection{Зачем это нужно?} \begin{itemize} \item Удобство кода: не нужно явно каждый раз в конце тела функции освобождать ресурсы и учитывать какие ресурсы успели захватиться, а какие нет. Когда выполнение текущего блока будет завершено, локальные объекты RAII-классов удалятся и необходимы ресурсы освободятся автоматически. \item Безопасность исключений: Если вызывается исключение, то гарантируется, что стек очиститься и все локальные объекты удаляться, а значит и освободятся ресурсы. @@ -172,41 +107,23 @@ \subsection{Что это дает?} \textcolor{red}{NB}) Это идиома работает не только в С++, а любом языке с предсказуемым временем жизни объектов. - -Вот небольшой пример RAII-класса, который будет управлять файлом. -Ресурсом является данные типа FILE (формат файла в Си). -\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -class File { -public: - File(char const *filename, char const *mode) - : _file(fopen(filename, mode)) { } //захват ресурса - - ~File() { - fclose(_file); // освобождение ресурса - } - - File(File const &) = delete; - File operator=(File const &) = delete; - -private: - FILE *_file; -}; -\end{minted} - -RAII часто встречается стандартной библиотеке: +\textcolor{red}{NB}) RAII часто встречается стандартной библиотеке: \begin{itemize} \item Smart pointers -- инкапсулируют несколько видов управления памятью -\item i/ofstream -- инкапсулиет оправление файлом -\item Различные контейнеры stl -- инкапсулируют управление памятью, для хранение объектов. +\item i/ofstream -- инкапсулиет управление файлом +\item Различные контейнеры stl -- инкапсулируют управление памятью, для хранение объектов в структурах данных. \end{itemize} \subsection{Best practice} - -Вернемся к предыдущему примеру File. +\begin{itemize} +\item +Начиная с с++11 есть возможность хранить RAII-классы в контейнерах с помощью механизма перемещения, для этого необходимо реализовать конструктор перемещения. +\item +Вернемся к предыдущему примеру Reader. Пусть в конструкторе есть код, который может сгенерировать исключение, тогда возникает проблема освобождения ресурса. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -File::File(char const *filename, char const *mode) +Reader::Reader(char const *filename, char const *mode) : _file(fopen(filename, mode)) { //код допускающий исключение } @@ -214,11 +131,11 @@ \subsection{Best practice} Если в теле конструктора происходит исключение, то деструктор не вызовется, так как объект не считается созданным. Что делать? Решение № 1 -Написать try-catch +Написать try-catch: \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -class File { +class Reader { public: - File(char const *filename, char const *mode) try + Reader(char const *filename, char const *mode) try : _file(fopen(filename, mode)) { //код допускающий исключение } @@ -226,7 +143,7 @@ \subsection{Best practice} destruct_obj(); } - ~File() { + ~Reader() { destruct_obj(); } @@ -237,52 +154,51 @@ \subsection{Best practice} FILE * _file; }; \end{minted} -Минусы решения: код раздулся. +Но это вызывает сильное раздувание кода. Решение № 2 Можно сделать отдельный подкласс, который хранит в себе ресурс. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -class File { - struct FileHandle { - FileHandle(FILE *fh) +class Reader { + struct ReaderHandle { + ReaderHandle(FILE *fh) : _fh(fh) { } - ~FileHandle() { + ~ReaderHandle() { fclose(_fh); } FILE *_fh; }; public: - File(char const * filename, char const * mode) + Reader(char const * filename, char const * mode) : _file(fopen(filename, mode)) { // код допускающий исключения } - ~File() = default; + ~Reader() = default; private: - FileHandle _file; + ReaderHandle _file; }; \end{minted} - Теперь все тоже хорошо, так как при возникновении исключения, вызовутся деструкторы от все членов класса. Решение № 3 (начиная с С++11) Делегирующий конструктор. \begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++} -class File +class Reader { - File(FILE * file) + Reader(FILE * file) : _file(file) { } public: - File(char const * filename, char const * mode) - : File(fopen(filename, mode)) { + Reader(char const * filename, char const * mode) + : Reader(fopen(filename, mode)) { // код допускающий исключения } - ~File() { + ~Reader() { fclose(_file); } @@ -290,5 +206,5 @@ \subsection{Best practice} FILE *_file; }; \end{minted} - -Дело в том, что если мы в конструкторе вызываем другой конструктор, то после его выполнения объект считается созданным и уничтожится автоматически. +Если возникнет исключение, то объект к этому времени будет считаться созданным, так как один конструктор уже отработал. Благодаря этому он уничтожится автоматически. +\end{itemize}