nullptr
Ключевое nullptr слово указывает константу указателя NULL типа std::nullptr_t , которая может быть преобразована в любой необработанный тип указателя. Хотя ключевое слово nullptr можно использовать без добавления заголовков, если код использует тип std::nullptr_t , его необходимо определить, включив заголовок <cstddef> .
Ключевое nullptr слово также определено в C++/CLI для приложений управляемого кода и не является взаимозаменяемым с ключевым словом ISO Standard C++. Если код может быть скомпилирован с помощью /clr параметра компилятора, предназначенного для управляемого кода, используйте __nullptr в любой строке кода, где необходимо гарантировать, что компилятор использует собственную интерпретацию C++. Дополнительные сведения см. в статьях nullptr (C++/CLI и C++/CX).
Комментарии
Избегайте использования NULL или нуля ( 0 ) в качестве константы указателя NULL; nullptr менее уязвимы для неправильного использования и лучше работает в большинстве ситуаций. Например, если для функции func(std::pair<const char *, double>) произвести вызов func(std::make_pair(NULL, 3.14)) , возникнет ошибка компилятора. Макрос NULL разворачивается до 0 , чтобы вызов std::make_pair(0, 3.14) возвращал std::pair<int, double> , который не может быть std::pair<const char *, double> преобразован в тип параметра в func . Вызов func(std::make_pair(nullptr, 3.14)) успешно компилируется, поскольку std::make_pair(nullptr, 3.14) возвращает значение std::pair<std::nullptr_t, double> , которое допускает преобразование в тип std::pair<const char *, double> .
Урок №81. Нулевые указатели
Как и в случае с обычными переменными, указатели не инициализируются при создании. Если значение не было присвоено, то указатель по умолчанию будет указывать на любой адрес, содержимым которого является мусор.
Нулевое значение и нулевые указатели
Помимо адресов памяти, есть еще одно значение, которое указатель может хранить: значение null. Нулевое значение (или «значение null») — это специальное значение, которое означает, что указатель ни на что не указывает. Указатель, содержащий значение null, называется нулевым указателем.
В языке C++ мы можем присвоить указателю нулевое значение, инициализируя его/присваивая ему литерал 0 :
Поскольку значением нулевого указателя является нуль, то это можно использовать внутри условного ветвления для проверки того, является ли указатель нулевым или нет:
Совет: Инициализируйте указатели нулевым значением, если не собираетесь присваивать им другие значения.
Разыменование нулевых указателей
Как мы уже знаем из предыдущего урока, разыменование указателей с мусором приведет к неожиданным результатам. С разыменованием нулевого указателя дела обстоят так же. В большинстве случаев вы получите сбой в программе.
В этом есть смысл, ведь разыменование указателя означает, что нужно «перейти к адресу, на который указывает указатель, и достать из этого адреса значение». Нулевой указатель не имеет адреса, поэтому и такой результат.
Макрос NULL
В языке Cи (но не в C++) есть специальный макрос препроцессора с именем NULL, который определен как значение 0 . Хоть он и не является частью языка C++, его использование достаточно распространено, и должно работать в каждом компиляторе С++:
Однако, поскольку NULL является макросом препроцессора и, технически, не является частью C++, то его не рекомендуется использовать.
Ключевое слово nullptr в C++11
Обратите внимание, значение 0 не является типом указателя, и присваивание указателю значения 0 для обозначения того, что он является нулевым — немного противоречиво, вам не кажется? В редких случаях, использование 0 в качестве аргумента-литерала может привести к проблемам, так как компилятор не сможет определить, используется ли нулевой указатель или целое число 0 :
Для решения этой проблемы в C++11 ввели новое ключевое слово nullptr, которое также является константой r-value.
Начиная с C++11, при работе с нулевыми указателями, использование nullptr является более предпочтительным вариантом, нежели использование 0 :
Язык C++ неявно преобразует nullptr в соответствующий тип указателя. Таким образом, в вышеприведенном примере, nullptr неявно преобразуется в указатель типа int, а затем значение nullptr присваивается ptr .
nullptr также может использоваться для вызова функции (в качестве аргумента-литерала):
Совет: В C++11 используйте nullptr для инициализации нулевых указателей.
Тип данных std::nullptr_t в C++11
В C++11 добавили новый тип данных std::nullptr_t, который находится в заголовочном файле cstddef. std::nullptr_t может иметь только одно значение — nullptr ! Хотя это может показаться немного глупым, но это полезно в одном случае. Если вам нужно написать функцию, которая принимает аргумент nullptr, то какой тип параметра нужно использовать? Правильно! std::nullptr_t. Например:
Вам, вероятно, никогда это не придется использовать, но знать об этом стоит (на всякий пожарный).
Поделиться в социальных сетях:
Урок №80. Указатели
Комментариев: 9
с этими указателями….какие-то скороговорки для мозга..
Так и не понял, для чего нужен std::nullptr_t. Зачем его ввели.
Судя по тому, что я узнал за 81 урок и какое мнение сложилось о С++, ответ на Ваш вопрос: "просто так, на всякий пожарный"))))))
ps. 3 допустимых вида инициализации переменных, значит, вас не смущают?)))))
/>Юрий :
В этих уроках объясняется как база, так и нюансы, которые вы, скорее всего, не очень часто будете использовать на практике, но знать об этом стоит.
Каждая переменная или константа должна быть определенного типа.
Следовательно, у nullptr (как константы, а не ключевого слова) тоже должен быть какой-то тип.
В MSDN есть пример, показывающий зачем ввели дополнительный тип:
Например, если для функции func(std::pair<const char *, double>) произвести вызов func(std::make_pair(NULL, 3.14)), возникнет ошибка времени компиляции. Макрос NULL разворачивается в 0, поэтому вызов std::make_pair(0, 3.14) возвращает значение std::pair<int, double>, которое невозможно преобразовать к типу параметра std::pair<const char *, double> функции func(). Вызов func(std::make_pair(nullptr, 3.14)) успешно компилируется, поскольку std::make_pair(nullptr, 3.14) возвращает значение std::pair<std::nullptr_t, double>, которое допускает преобразование в тип std::pair<const char *, double>.
Зачем еще оно нужно:
Приходите на собеседование, и дают вам такой код
Десять возможностей C++11, которые должен использовать каждый C++ разработчик
В данной статье рассматривается ряд возможностей С++11, которые все разработчики должны знать и использовать. Существует много новых дополнений к языку и стандартной библиотеке, эта статья лишь поверхностно охватывает часть из них. Однако, я полагаю, что некоторые из этих новых функций должны стать обыденными для всех разработчиков С++. Подобных статей наверное существует много, в этой я предприму попытку составить список возможностей, которые должны войти в повседневное использование.
- auto
- nullptr
- range-based циклы
- override и final
- строго-типизированный enum
- интеллектуальные указатели
- лямбды
- non-member begin() и end()
- static_assert и классы свойств
- семантика перемещения
#1 — auto
До С++11, ключевое слово auto использовалось как спецификатор хранения переменной (как, например, register, static, extern ). В С++11 auto позволяет не указывать тип переменной явно, говоря компилятору, чтобы он сам определил фактический тип переменной, на основе типа инициализируемого значения. Это может использоваться при объявлении переменных в различных областях видимости, как, например, пространство имен, блоки, инициализация в цикле и т.п.
Использование auto позволяет сократить код (если, конечно, тип не int , который на одну букву меньше). Подумайте об итераторах STL, которые вы должны были всегда писать для прохода контейнеров. Таким образом, это делает устаревшим определение typedef только ради простоты.
Стоить отметить, что возвращаемое значение не может быть auto . Однако, вы можете использовать auto вместо типа возвращаемого значения функции. В таком случае, auto не говорит компилятору, что он должен определить тип, он только дает ему команду искать возвращаемый тип в конце функции. В примере ниже, возвращаемый тип функции compose — это возвращаемый тип оператора +, который суммирует значения типа T и E .
#2 — nullptr
Раньше, для обнуления указателей использовался макрос NULL, являющийся нулем — целым типом, что, естественно, вызывало проблемы (например, при перегрузке функций). Ключевое слово nullptr имеет свой собственный тип std::nullptr_t , что избавляет нас от бывших проблем. Существуют неявные преобразования nullptr к нулевому указателю любого типа и к bool (как false ), но преобразования к целочисленных типам нет.
#3 — range-based циклы
В С++11 была добавлена поддержка парадигмы foreach для итерации по набору. В новой форме возможно выполнять итерации в случае, если для объекта итерации перегружены методы begin() и end() .
Это полезно, когда вы просто хотите получить элементы массива/контейнера или сделать с ними что-то, не заботясь об индексах, итераторах или кол-ве элементов.
#4 — override и final
Мне всегда не нравились виртуальные функции в С++. Ключевое слово virtual опционально и поэтому немного затрудняло чтение кода, заставляя вечно возвращаться в вершину иерархии наследования, чтобы посмотреть объявлен ли виртуальным тот или иной метод. Я всегда использовал этой ключевое слово так же и в производных классах (и поощрял людей, кто так делал), чтобы код был понятнее. Тем не менее, есть ошибки, которые могут все таки возникнуть. Возьмем следующий пример:
D::f переопределяет B::f . Однако они имеют разную сигнатуру, один метод принимает short , другой — int , поэтому B::f — это просто другой метод с тем же именем, перегруженный, а не переопределенный. Таким образом, работая через указатель на базовый класс, Вы можете вызвать f() и ожидать вывода «переопределенного» вами метода: «D::f», однако вывод будет «B::f».
Вот другая возможная ошибка: параметры одни и те же, но в базовом классе метод константный, а в производном — нет.
И снова это две перегруженные, а не переопределенные функции.
К счастью, теперь есть способ избавиться от этих ошибок. Были добавлены два новых идентификатора (не ключевые слова): override , для указания того, что метод является переопределением виртуального метода в базовом классе и final , указывающий что производный класс не должен переопределять виртуальный метод. Первый пример теперь выглядит так:
Теперь это вызовет ошибку при компиляции (точно так же, если бы вы использовали override во втором примере):
С другой стороны, если вы хотите сделать метод, не предназначенный для переопределения (ниже в иерархии), его следует отметить как final . В производном классе можно использовать сразу оба идентификатора.
Функция, объявленная как final , не может быть переопределена функцией F::f() — в этом случае, она переопределяет метод базового класса ( В ) для класса D .
#5 — строго-типизированный enum
У «традиционных» перечислений в С++ есть некоторые недостатки: они экспортируют свои значения в окружающую область видимости (что может привести к конфликту имен), они неявно преобразовываются в целый тип и не могут иметь определенный пользователем тип.
Эти проблемы устранены в С++11 с введением новой категории перечислений, названных strongly-typed enums. Они определяются ключевым словом enum class . Они больше не экспортируют свои перечисляемые значения в окружающую область видимости, больше не преобразуются неявно в целый тип и могут иметь определенный пользователем тип (эта опция так же добавлена и для «традиционных» перечислений»).
#6 — интеллектуальные указатели
-
: должен использоваться, когда ресурс памяти не должен был разделяемым (у него нет конструктора копирования), но он может быть передан другому unique_ptr : должен использоваться, когда ресурс памяти должен быть разделяемым : содержит ссылку на объект, которым управляет shared_ptr , но не осуществляет подсчет ссылок; позволяет избавиться от циклической зависимости
Второй пример демонстрирует shared_ptr . Использование похоже, хотя семантика отличается, поскольку теперь владение совместно используемое.
Первое объявление эквивалентно следующему:
make_shared — это функция, имеющая преимущество при выделении памяти для совместно используемого объекта и интеллектуального указателя с единственным выделением, в отличие от явного получения shared_ptr через конструктор, где требуется, по крайней мере, два выделения. Из-за этого может произойти утечка памяти. В следующем примере как раз это демонстрируется, утечка может произойти в случае, если seed() бросит исключение.
Эта проблема решается использованием make_shared .
И, наконец, пример с weak_ptr . Заметьте, что вы должны получить shared_ptr для объекта, вызывая lock() , чтобы получить доступ к объекту.
#7 — лямбды
В новом стандарте наконец-то была добавлена поддержка лямбда-выражений. Мы можете использовать лямбды везде, где ожидается функтор или std::function . Лямбда, вообще говоря, представляет собой более короткую запись функтора, что-то вроде анонимного функтора. Подробнее можно почитать, например, на MSDN.
Теперь немного более хитрые — рекурсивные лямбды. Представьте лямбду, представляющую функцию Фибоначчи. Если вы попытаетесь записать ее, используя auto , то получите ошибку компиляции:
Здесь имеет место циклическая зависимость. Чтобы избавиться от нее, необходимо явно определить тип функции, используя std::function .
#8 — non-member begin() и end()
Вы, вероятно, заметили, что в примерах ранее, я использовал функции begin() и end() . Это новое дополнение к стандартной библиотеке. Они работают со всеми контейнерами STL и могут быть расширены для работы с любым типом.
Давайте возьмем, например, предыдущий пример, где я выводил вектор и затем искал первый нечетный элемент. Если std::vector заменить С-подобным массивом, то код будет выглядеть так:
С begin() и end() его можно переписать следующим образом:
Это почти полностью идентично коду с std::vector . Таким образом, мы можем написать один универсальный метод для всех типов, которые поддерживаются функциями begin() и end() .
#9 — static_assert и классы свойств
static_assert проверяет утверждение во время компиляции. Если утверждение — истина, то ничего не происходит. Если — ложь, то компилятор выводит указанное сообщение об ошибке.
static_assert становится более полезен, когда используется с классами свойств. Это набор классов, которые предоставляют информацию о типах во время компиляции. Они доступны в заголовке <type_traits> . Есть несколько видов классов в этом заголовке: классы-помощники, классы преобразований и непосредственно классы свойств.
В следующем примере, функция add , как предполагается, работает только с целочисленными типами.
Однако, при компиляции не возникнет ошибки, если написать следующее:
Программа просто выведет «4.14» и «е». Используя static_assert , эти две строки вызовут ошибку во время компиляции.
#10 — семантика перемещения
Это — еще одна важная тема, затронутая в С++11. На эту тему можно написать несколько статей, а не абзацев, поэтому я не буду сильно углубляться.
C++11 ввел понятие rvalue ссылок (указанных с &&), чтобы отличать ссылка на lvalue (объект, у которого есть имя) и rvalue (объект, у которого нет имени). Семантика перемещения позволяет изменять rvalues (ранее они считались неизменными и не отличались от типов const T&).
Класс/структура раньше имели некоторые неявные функции-члены: конструктор по умолчанию (если другой конструктор не определен), конструктор копирования и деструктор. Конструктор копирования выполняет поразрядное копирование переменных. Это означает, что если у вас есть класс с указателями на какие-то объекты, то конструктор копирования скопирует указатели, а не объекты, на которые они указывают. Если вы хотите получить в копии именно объекты, а не лишь указатели на них, вы должны это явно описать в конструкторе копирования.
Конструктор перемещения и оператор присваивания перемещения — эти две специальные функции принимают параметр T&&, который является rvalue. Фактически, они могут изменять объект.
Следующий пример показывает фиктивную реализацию буфера. Буфер идентифицируется именем, имеет указатель (обернутый в std::unique_ptr ) на массив элементов типа Т и переменную, содержащую размер массива.
Конструктор копирования по умолчанию и оператор присваивания копии должны быть вам знакомы. Новое в С++11 — это конструктор перемещения и оператор присваивания перемещения, Если вы выполните этот код, то увидите, что когда создается b4 — вызывается конструктор перемещения. Кроме того, когда b1 присваивается значение — вызывается оператор присваивания перемещения. Причина — значение, возвращаемое функцией getBuffer() — rvalue.
Вы, вероятно, заметили использование std::move в конструкторе перемещения, при инициализации имени переменной и указателя на буфер. Имя — это строка std::string и std::string также реализует семантику перемещения. То же самое касается и unique_ptr . Однако, если бы мы записали просто _name(temp._name) , то был бы вызван конструктор копирования. Но почему в этом случае не был вызван конструктор перемещения для std::string ? Дело в том, что даже если конструктор перемещения для Buffer был вызван с rvalue, внутри конструктора это все равно представляется как lvalue. Чтобы сделать его снова rvalue и нужно использовать std::move . Эта функция просто превращает ссылку lvalue в rvalue.