forked from sorokin/cpp-notes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nullptr.tex
50 lines (46 loc) · 5.68 KB
/
nullptr.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
\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}, проверить это можно следующим кодом:
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
template <typename T>
void f(T arg);
void g(int);
void g(void*);
f(0); // calls f<int>(0)
g(0); // calls g(int)
\end{minted}
Хотя 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}
Если {\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 это правило покрутили и теперь только целочисленный литерал может использоваться в качестве нулевого указателя.
До появления в языке шаблонов, два разных применения 0 не создавали проблем. Но с появлением шаблонов возникла следующая проблема при форвандинге. Предположим мы хотим, чтобы {\bf f} форвардил свой аргумент в {\bf g}.
\begin{minted}[linenos, frame=lines, framesep=2mm, tabsize = 4, breaklines]{c++}
template <typename T>
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<int>(0)} и {\bf arg} внутри {\bf f} не является compile-time константой, следовательно он не может быть преоразован в {\bf void*} принимаемый {\bf g}.
Для разрешения этой проблемы в 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 существует макрос \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.