Что такое спецификатор типа зачем он нужен

8.2 Спецификаторы Типа

8.2 Спецификаторы Типа

Спецификаторами типов (спецификатор_типа) являются:

простое_имя_типа спецификатор_класса enum-спецификатор сложный_спецификатор_типа const

Слово const можно добавлять к любому допустимому спецфикатору_типа. В остальных случаях в описании может быть дано не более одного спецификатора_типа. Объект типа const не яляется lvalue. Если в описании опущен спецификатор типа, он принимается int.

простое_имя_типа: char short int long unsigned float double const void

Слова long, short и unsigned можно рассматривать как прилагательные. Они могут применяться к типу int; unsigned может также применяться к типам char, short и long.

Спецификаторы класса и перечисления обсуждаются в #8.5 и #8.10 соответственно.

сложный_спецификатор_типа: ключ typedef-имя ключ идентификатор

ключ: class struct union enum

Сложный спецификатор типа можно использовать для ссылки на имя класса или перечисления там, где имя может быть скрыто локальным именем. Например:

Если имя класса или перечисления ранее описано не было, сложный_спецификатор_типа работает как описание_имени, см. #8.8.

Читайте также

12.5.1 Редакторы типа vi

12.5.1 Редакторы типа vi Редактор vi (или его несколько доработанные потомки) по умолчанию включается в любую UNIX-подобную систему, в том числе и во все дистрибутивы Linux. Все приверженцы UNIX, имеющие значительный стаж работы с этими ОС, знают и используют этот редактор. Описание

R.7.1.6 Спецификация типа

R.7.1.6 Спецификация типа К спецификации типа относятся:спецификация-типа: имя-простого-типа спецификация-класса спецификация-перечисления спецификация-сложного-типа :: имя-класса const volatileПри описании объекта служебные слова const и volatile можно добавить к любой законной

R.14 ШАБЛОНЫ ТИПА

R.14.1 Шаблоны типа

R.14.4 Шаблоны типа для функций

R.14.4 Шаблоны типа для функций Шаблон типа для функции определяет как будет строиться функция. Например, семейство функций sort можно описать следующим образом:template‹class T› void sort(vector‹T›);Шаблон типа для функции порождает неограниченное множество перегруженных функций.

R.17.8 Шаблоны типа

Процедуры типа Sub

Процедуры типа Sub Термин процедура типа Sub кажется мне довольно неуклюжим, но он достаточно точно отражает суть дела. В некоторых языках программирования суб процедурам и, или подпрограммами, называются процедуры, вызываемые главными процедурами. В VBA, хотя процедуры типа

Вызов процедур типа Sub

Процедуры типа Function

Отличия процедур типа Function от процедур типа Sub

Отличия процедур типа Function от процедур типа Sub Между процедурами типа Function и типа Sub есть одно существенное отличие: в процедуре типа Function обязательно где-то должен присутствовать по крайней мере один оператор, задающий значение этой функции. При этом используется имя

Тестирование типа CarControl

Тестирование типа CarControl При запуске или отладке проекта Windows Control Library в Visual Studio 2005 иcпользуется UserControl Test Container (испытательный контейнер пользовательских элементов управления). Это управляемый вариант теперь уже устаревшего ActiveX Control Test Container (испытательный контейнер

Объявление типа

Объявление типа Существует два особых вида объявления, в которых объявляется не переменная или функция, а тип данных. Первый вид позволяет определить тег и элементы структуры, объединения или перечислимого типа. После такого объявления имя типа (тег) может быть

Три типа «одежки»

Три типа «одежки» Все многообразие видов кейсов для ноутбуков можно свести к трем позициям:Сумка. Классическими портфелями-сумками заполнено более 90 % рынка. Может иметь вид женской сумочки, портфеля, кофра, саквояжа, дипломата, чемодана. Имейте в виду, что элементы

3.2.5 Преобразование типа

3.2.5 Преобразование типа Бывает необходимо явно преобразовать значение одного тпа в значение другого. Явное преобразование типа дает значние одного типа для данного значения другого типа. Например:float r = float(1);перед присваиванием преобразует целое значение 1 к знчению с

7.2.7 Поля Типа

7.2.7 Поля Типа Чтобы использовать производные классы не просто как удобную сокращенную запись в описаниях, надо разрешить следющую проблему: Если задан указатель типа base*, какому проиводному типу в действительности принадлежит указываемый обект? Есть три основных

8.1 Спецификаторы Класса Памяти

8.1 Спецификаторы Класса Памяти Спецификаторы – это:спецификатор_класса_памяти: auto static extern registerОписания, использующие спецификаторы auto, static и register также служат определениями тем, что они вызывают рзервирование соответствующего объема памяти. Если описание extern не

Источник

Форматированный ввод и вывод

Форматированный вывод

Сегодня мы рассмотрим две важные функции форматированного ввода и вывода. Устройство и работу этих функций полностью можно понять только после изучения работы с указателями и функций с переменным числом параметров. Но пользоваться этими функциями необходимо уже сейчас, так что некоторые моменты придётся пропустить.

Функция проходит по строке и заменяет первое вхождение % на первый аргумент, второе вхождение % на второй аргумент и т.д. Далее мы будем просто рассматривать список флагов и примеры использования.

Общий синтаксис спецификатора формата
%[флаги][ширина][.точность][длина]спецификатор
Спецификатор – это самый важный компонент. Он определяет тип переменной и способ её вывода.

Таб. 1 Спецификатор типа.

СпецификаторЧто хотим вывестиПример
d или iЦелое со знаком в в десятичном виде392
uЦелое без знака в десятичном виде7235
oБеззнаковое в восьмеричном виде657
xБеззнаковое целое в шестнадцатеричном виде7fa
XБеззнаковое целое в шестнадцатеричном виде, верхний регистр7FA
f или FЧисло с плавающей точкой3.4563745
eЭкспоненциальная форма для числа с плавающей точкой3.1234e+3
EЭкспоненциальная форма для числа с плавающей точкой, верхний регистр3.1234E+3
gКратчайшее из представлений форматов f и e3.12
GКратчайшее из представлений форматов F и E3.12
aШестнадцатеричное представление числа с плавающей точкой-0xc.90fep-2
AШестнадцатеричное представление числа с плавающей точкой, верхний регистр-0xc.90FEP-2
cБукваa
sСтрока (нуль-терминированный массив букв)Hello World
pАдрес указателяb8000000
nНичего не печатает. Аргументом должен быть указатель на signed int. По этому адресу будет сохранено количество букв, которое было выведено до встречи %n
%Два идущих друг за другом процента выводят знак процента%

Суб-спецификатор длины изменяет длину типа. В случае, если длина не совпадает с типом, по возможности происходит преобразование до нужного типа.

Таб. 5 Длина.

спецификаторы
Длинаd, iu o x Xf F e E g G a Acspn
(none)intunsigned intdoubleintchar*void*int*
hhsigned charunsigned charsigned char*
hshort intunsigned short intshort int*
llong intunsigned long intwint_twchar_t*long int*
lllong long intunsigned long long intlong long int*
jintmax_tuintmax_tintmax_t*
zsize_tsize_tsize_t*
tptrdiff_tptrdiff_tptrdiff_t*
Llong double

Форматированный ввод

Как и в printf, ширина, заданная символом * ожидает аргумента, который будт задавать ширину. Флаг длина совпадает с таким флагом функции printf.

Кроме функций scanf и printf есть ещё ряд функций, которые позволяют получать вводимые данные

Это не полный набор различных функций символьного ввода и вывода. Таких функций море, но очень многие из них небезопасны, поэтому перед использованием внимательно читайте документацию.

Непечатные символы

В си определён ряд символов, которые не выводятся на печать, но позволяют производить форматирование вывода. Эти символы можно задавать в виде численных значений, либо в виде эскейп-последовательностей: символа, экранированного обратным слешем.

Источник

Спецификаторы типов

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Язык Си поддерживает определения для множества базовых ти­пов данных, называемых «основными» типами. Названия этих типов перечислены в Табл. 4.1.

Типы целых Типы плавающих Другие типы

signed char float void

signed short intsigned long int

unsignet short int unsigned long int

Табл. 4.1. Основные типы.

Перечислимые типы также рассматриваются как основные типы. Спецификаторы перечислимых типов рассмотрены в разделе 4.7.1. Ти­пы signed char, signed int, signed short int и signed long int

вместе с соответствующими двойниками unsigned называются типами целых.

Спецификаторы типов float и double относятся к типу «плава­ющих». В об»явлениях переменых и функций можно использовать любые спецификаторы «целый» и «плавающий».

Тип void может быть использован только для об»явления функ­ций, которые не возвращают значения. Типы функций рассмотрены в разделе 4.4.

Можно задать дополнительные спецификаторы типа путем об»яв­ления typedef, описанного в разделе 4.7.2.

При записи спецификаторов типов допустимы сокращения как показано в табл. 4.2. В целых типах ключевое слово signed может быть опущено. Так, если ключевое слово unsigned опускается в за­писи спецификатора типа, то тип целого будет знаковым, даже если опущено ключевое слово signed.

В некоторых реализациях могут быть использованы опции ком­пилятора, позволяющие изменить умолчание для типа char со знако­вого на беззнаковый. Когда задана такая опция, сокращение char имеет то же самое значение, что и unsigned char, и следовательно ключевое слово sidned должно быть записано при об»явлении сим­вольной величины со знаком.

Спецификатор типа Сокращение

signed int signed, int

signed short int short, signed short

signed long int long, signed long

unsigned int unsigned

unsigned short int unsignet short

unsignet long int unsignet long

Табл. 4.2. Спецификаторы и сокращения

Замечание: в этом руководстве в основном используются сок­ращенные формы, перечисленные в Табл. 4.2, при этом предполагает­ся, что char по умолчанию знаковый.

В табл. 4.3 для каждого типа приведены: размер распределяе­мой памяти и области значений переменных для данного типа. Пос­кольку тип void не представляет переменных, он не включен в эту таблицу.

Тип Представление Область значений

unsigned char 1 байт 0 до 255

unsigned зависит от

unsigned short 2 байта 0 до 65535

unsigned long 4 байта 0 до 4.294.967.295

float 4 байта IEEE стандартное

double 8 байт IEEE стандартное

Табл 4.3 Размер памяти и область значений типов

имеют символьные эквиваленты. Аналогично, тип unsigned char может запоминать величины с областью значений от 0 до 255.

Заметим, что представление в памяти и область значений для типов int и unsigned int не определены в языке Си. По умолчанию размер int (со знаком и без знака) соответствует реальному разме­ру целого на данной машине. Например, на 16-ти разрядной машине тип int всегда 16 разрядов или 2 байта. На 32-ух разрядной машине тип int всегда 32 разряда или 4 байта. Таким образом, тип int эк­вивалентен типам short int или long int в зависимости от реализа­ции.

Аналогично, тип unsigned int эквивалентен типам unsigned short или unsigned long. Спецификаторы типов int и unsigned int широко используются в программах на Си, поскольку они позволяют наиболее эффективно манипулировать целыми величинами на данной машине.

Однако, размер типов int и unsigned int переменный, поэтому программы, зависящие от специфики размера int и unsigned int мо­гут быть непереносимы. Переносимость кода можно улучшить путем включения выражений с sizeof операцией.

Источник

Спецификаторы, квалификаторы и шаблоны

Такое количество ключевых слов введет в ступор любого неподготовленного разработчика. Но на C++ Russia 2019 Piter Михаил Матросов (mmatrosov) разложил по полочкам квалификаторы и спецификаторы при объявлении переменных и функций.

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

Из доклада вы узнаете:

Далее — повествование от лица спикера.

Немного теории

Проведем небольшой теоретический экскурс, чтобы понять дальнейший материал доклада.

Посмотрим, как происходит сборка программы на C++:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

В исходные cpp-файлы включают заголовочные hpp-файлы. Во время сборки первым начинает работу препроцессор. Из исходных файлов он формирует единицы трансляции (translation units), в которые собраны все заголовочные файлы (headers), а за ними идет тело cpp-файла. Конечно, компилятор по умолчанию не сохраняет их в явном виде на жестком диске, а они лежат в оперативной памяти.

Когда единицы трансляции сформированы, компилятор выполняет компиляцию каждой независимо. В результате для каждой единицы трансляции компилятор получает объектный файл. Результат компиляции передается компоновщику (linker), который собирает независимые объектные файлы в итоговую программу или библиотеку.

Теперь вспомним, что такое объявление и определение. У сущностей, например переменных и функций, может быть сколько угодно объявлений, но только одно определение (за исключением некоторых случаев, где допускается несколько определений, но все они должны быть одинаковыми).

Перейдем к понятию linkage. Рассмотрим простенькую программу. В файле a.cpp содержится функция sqr():

А в файле b.cpp находится ее объявление и некоторая функция check():

Программа скомпилируется, потому что определение функции в a.cpp имеет external linkage. Поэтому когда компилятор создаст объектные файлы, в a.obj он положит определение функции sqr(), а в b.obj — объявление функции с пометкой, что в каком-то файле лежит определение этой функции sqr(), и компоновщик его найдет. Если же в объявление функции мы добавим ключевое слово static, то программа не соберется из-за ошибки линковки. Так как функция sqr() будет иметь internal linkage, то есть будет недоступна в других единицах трансляции, и компоновщик её не найдёт.

Кроме external linkage и internal linkage сущность может иметь статус no linkage. Така сущность доступна только в области видимости, в которой объявлена. Типичный пример — локальная переменная.

Теперь вспомним типы storage duration в C++:

Storage duration и linkage контролируются рядом ключевых слов (storage class specifiers) — static, extern, thread_local и mutable. Mutable не имеет отношения к Storage duration и linkage, и об этом в докладе больше не будет, но он формально является storage class specifier.

На теоретическом экскурсе мы ответили на три вопроса:

Internal и external linkage

Рассмотрим пример. В некотором заголовочном файле common.hpp объявили две константы:

А в исходные файлы a.cpp и b.cpp включили этот hpp-файл:

Это не скомпилируется, потому что есть несколько определений одного и того же имени name. Однако компилятор не ругается на thickness. Почему?

Any of the following names declared at namespace scope have internal linkage:

Можно было бы подумать, что обе переменные const-qualified, поэтому имеют internal linkage, и их определения в единицах трансляции должны быть независимы. Однако name — это указатель, и ключевое слово const относится к объекту, на который он указывает. То есть он является указателем на константу, но не является константным указателем. Чтобы сделать его константным, нужно будет изменить запись:

Теперь name стал константным указателем на константу, получил internal linkage, и программа собирается без проблем.

Давайте изменим пример:

Это скомпилируется, потому что name — константный символ, а спецификатор constexpr для объекта влечет за собой const, плюс linkage constexpr сущностей в явном виде описан в том же абзаце. Поэтому обе константы имеют internal linkage.

Any of the following names declared at namespace scope have internal linkage:

Перейдем к следующему примеру. В common.hpp оставим name и добавим функцию getName(), которая доступна из разных единиц трансляции:

В a.cpp мы сравниваем адреса буферов, которые возвращают name.data() и getName():

В b.cpp мы определим функцию getName():

Мы знаем, что name доступна в обеих единицах трансляции. Но одинаковая ли переменная в обоих случаях? Нет, программа напечатает false, потому что для каждой единицы трансляции создается отдельная копия name, а сравнение в dumbCmp() идет не по значению, а по адресу в памяти.

Чтобы программа выдала true, добавим к определению name спецификатор inline:

В этом случае во всей программе будет только один объект name, и эта переменная получит особенный external linkage. В каждой единице трансляции все еще будет своя копия переменной на этапе компиляции, но когда этот символ попадет в объектный файл, то он получит пометку, что это weak символ. И компоновщик при объединении объектных файлов в программу выберет из нескольких одинаковых символов только один. В стандарте нет понятия external weak linkage, поэтому формально переменная будет иметь external linkage. Однако если попросить утилиты типа nm или dumpbin показать информацию об этой переменной в объектном файле, то они выведут именно external weak linkage.

Это не скомпилируется, потому что в каждой единице трансляции будет свое определение функции. Чтобы программа скомпилировалась, добавим спецификатор constexpr:

Если функция constexpr-qualified, то она считается inline. А спецификатор inline для функций также влечет external weak linkage. В современном C++ inline в первую очередь означает, что компоновщик выберет только один экземпляр данной сущности.

Представим, что мы пишем какой-то main.cpp, где создаем класс Local и объявляем в нем функцию foo():

Но другой разработчик в other.cpp тоже независимо завел класс Local и функцию foo():

В итоге в программе есть несколько определений одного и того же символа в разных единицах трансляции. Причём эти определения разные. Такая ситуация приводит к неопределённому поведению. То есть формально программа может напечатать всё, что угодно. На практике же мы увидим, например, следующее:

GCC считает, что Local в разных файлах — это один и тот же класс, в нём есть функция foo(). Компилятор знает, что определения этой функции в разных файлах обязаны быть одинаковыми. Поэтому он взял первое попавшееся — из main.cpp. Другой компилятор мог бы вывести что-то другое.

Эта проблема произошла из-за того, что класс Local имел external linkage. Чтобы исправить программу, положим классы в анонимное пространство имен (unnamed namespace):

Все сущности, которые оказываются в анонимном пространстве имен, всегда имеют internal linkage, то есть ничего из translation unit не может просочиться наружу. Поэтому программа будет работать так, как мы ожидаем:

Собираем в кучу

Посмотрим, какие существуют допустимые комбинации между storage duration и linkage:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Для dynamic storage duration не имеет смысла концепция linkage, потому что мы выделяем объект в куче самостоятельно. Для automatic storage duration применимо только no linkage, ведь память под объект выделяется только при попадании в scope, то есть на этапе выполнения программы. Поэтому автоматические и динамические объекты мы не будем больше рассматривать, и говорить будем только о статических и thread_local объектах.

Чтобы определить, какой storage duration у объекта, можно использовать блок-схему:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Если сущность имеет спецификатор thread_local, то у нее thread storage duration. Если это не так, то нужно посмотреть на scope. Если переменная глобальная, то у нее всегда static storage duration. Для локальной переменной или члена класса проверяем наличие спецификатора static. Если он есть, то переменная статическая, иначе — автоматическая.

Посмотрим, как эффекты, которые мы пронаблюдали, собираются вместе для разных видов сущностей:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Колонки в таблице соответствуют разным видам сущностей, а строки — свойствам, которые будут применяться к сущности. Свойства необходимо рассматривать по порядку, потому что те свойства, что ниже, имеет более высокий приоритет чем те, что выше.

Для примера рассмотрим глобальную переменную. Из таблицы мы можем понять:

А под записью Required подразумевается, что эти сущности обязаны иметь ключевое слово из соответствующего свойства, чтобы вообще попасть в эту таблицу. Например, если у поля класса не будет спецификатора static, то оно вообще не попадёт в эту таблицу.

Спецификатор extern

В примере, где мы сравнивали буферы, мы использовали inline, чтобы программа вывела true. Однако это не единственный способ решения задачи.

До C++17 не было inline-переменных, и мы могли объявить переменную name как extern:

Тогда бы переменная получила external linkage и превратилась в объявление (declaration). Но в этом случае необходимо где-то добавить явное определение для переменной name, и мы вставляем его в a.cpp :

Таким образом мы бы получили тот же результат выполнения программы.

Какими свойствами обладает extern?

Но это довольно специфический момент, и обычно вместо extern лучше использовать inline.

Добавим extern в таблицу комбинаций свойств и сущностей:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Для не глобальных сущностей extern неприменим. Для глобальных же функций данный спецификатор излишен, потому что любое объявление глобальной функции по умолчанию является extern. Но для глобальных переменных спецификатор будет работать, и для переменной он будет указывать external linkage и превращать ее в объявление переменной.

Practice time

От теории перейдем к практике. Рассмотрим такой класс:

Посмотрим на таблицу. Нас интересует колонка member variable. Какие выводы мы можем сделать?

Не совсем. constexpr и static находятся в разных мирах. constexpr действителен только при компиляции, и после этого процесса от constexpr не остается и следа (ну, точнее, от него останется const или inline, в соответствии с таблицей свойств). Но когда программа начинает выполняться, те же самые переменные, которые использовались на этапе компиляции, начинают существовать уже на этапе выполнения. К ним становится применим спецификатор static, потому что только на стадии выполнения у них есть storage duration.

Шаблоны

Функции, классы и переменные могут быть шаблонами. Однако важно понимать, что не бывает шаблонных сущностей (template entity), а есть только шаблоны сущностей (entity template). Сравним функцию и шаблон:

У шаблона есть неявные инстанциации. Но компоновщик сам позаботится о них в разных модулях трансляции. Их linkage не так важен и даже не всегда понятен.

Перейдем к примеру. Заведем три шаблона переменных в заголовочном файле:

Включаем hpp-файл в два cpp-файла. Далее инстанцируем переменные: b, cb и icb. В каждой единице трансляции мы берем адрес у этих инстанциаций и выводим. Компилятор clang выдал:

Мы видим одни и те же адреса. Значит, программа работала с одними и теми же объектами. Скомпилируем программу с помощью gcc и посмотрим результат:

Для const bool cb внезапно различаются адреса. Я даже задал вопрос на stackoverflow и получил интересный ответ:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Использовать inline для шаблонов функций нет смысла. Компилятор проигнорирует такой inline, а инструмент для статического анализа подскажет, что он лишний. Если вы делаете явную специализацию этой функции (а специализация уже является не шаблоном, а именно функцией), то указание inline имеет смысл, иначе использование специализации в разных единицах трансляции привело бы к множественному определению.

Как уже говорилось ранее, у шаблонов в большинстве случаев неявная инстанциация, достаточно поставить угловые скобки. Есть не очень известный, но полезный механизм — объявление явной инстанциации (explicit instantiation declaration).

Пусть в header.hpp есть некоторый шаблон большой сложной функции:

Мы можем написать extern template и указать сущность с конкретным типом:

Компилятор будет воспринимать это как объявление явной инстанциации. Если в какой-либо единице трансляции он встретит специализацию функции для этого типа, он не сделает неявную инстанциацию, а просто оставит пометку компоновщику, чтобы тот искал явную инстанициацию в других единицах трансляции. А это значительно быстрее.

Поскольку у нас есть объявление явной инстанциации, куда-то нужно будет поместить её определение:

Такой подход может заметно ускорить время сборки проекта. Если у вас есть сложная шаблонная сущность, которая инстанцируется для известного набора типов, то вы можете в явном виде описать инстанциации. Более того, если этот шаблон инстанцировать только с типами, для которых уже предоставили явную инстанциацию, то тело шаблона из заголовочного файла можно вообще убрать.

Долгий путь к const

Константы до C++17 могли быть объявлены в заголовочном файле кучей разных способов:

Тут вроде бы уже все знают, что так делать не стоит.

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

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

Весьма неплохой подход, но работает только для целочисленных типов.

Начиная с C++17 мы можем использовать inline, который будет работать для любого типа. В заголовочном файле это будет выглядеть так:

Если мы объявляем константу в cpp-файле, то она должна быть доступна только в текущей единице трансляции:

Убираем inline, иначе объявление константы может интерферировать с другой единицей трансляции. Кстати, в module interface unit в C++20 можно использовать тот же синтаксис.

Если константа — член класса, то она объявляется как static:

Если к константе нельзя применить constexpr, то придется вручную прописать inline, потому что для поля класса его компилятор не подставит, в отличие от функций.

Если же константа — локальная переменная, то синтаксис похож на объявление глобальной переменной, но со static:

Целых 8 вариантов. Но все не так сложно, как кажется. Асимметрия между constexpr и const наблюдается только в случае, когда константа — член класса.

Когда в светлом будущем, допустим, останутся только модули и не будет заголовочных файлов, останутся только эти варианты:

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

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Она описывает ровно те примеры, что мы разобрали выше.

Загадочный пример из описания

Рассмотрим пример, который был в описании доклада:

Попробуем его оптимизировать:

static для глобальной переменной даёт internal linkage. thread_local говорит о том, что будет thread storage duration. Поэтому x — это constexpr volatile шаблон переменной с thread storage duration и internal linkage (constexpr volatile variable template with thread storage duration and internal linkage).

Изменения в C++20

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

В C++ 20 добавляется еще один вид linkage — module linkage. external linkage становится module linkage, потому что это linkage внутри модуля, а все, что выходит за пределы модуля, становится external linkage.

Для удобства можно считать, что consteval функция недоступна на этапе компоновки и выполнения, не генерирует символа в объектном файле и является своеобразным функциональным макросом. На самом деле в стандарте вообще нет таких понятий, как “время компиляции” и “время выполнения”. Есть только “наблюдаемый эффект выполнения программы”. Однако формулировка consteval дана таким образом, чтобы реальные компиляторы имели возможность реализовать ожидаемое поведение.

Добавим consteval и constinit в таблицу:

Что такое спецификатор типа зачем он нужен. Смотреть фото Что такое спецификатор типа зачем он нужен. Смотреть картинку Что такое спецификатор типа зачем он нужен. Картинка про Что такое спецификатор типа зачем он нужен. Фото Что такое спецификатор типа зачем он нужен

Как жить с особенностями C++ и не сойти с ума

В этом году на конференции С++ Russia 2020 Moscow выступят сам создатель языка С++ Бьярне Страуструп и председатель комитета по стандартизации С++ Герб Саттер! Еще больше знаменитых спикеров можно будет увидеть по билету-абонементу, который дает доступ ко всем 8 конференциям летнего сезона.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *