Что такое спецификатор типа

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

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

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

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

Язык Си поддерживает определения для множества базовых ти­пов данных, называемых «основными» типами. Названия этих типов перечислены в Табл. 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++)

Объявление псевдонима можно использовать для объявления имени, которое будет использоваться в качестве синонима для ранее объявленного типа. (Этот механизм также называется псевдонимом типа). Этот механизм также можно использовать для создания шаблона псевдонима, который может быть особенно полезен для пользовательских распределительов.

Синтаксис

Примечания

identifier
Имя псевдонима.

type
Идентификатор типа, для которого создается псевдоним.

Псевдоним не вводит в программу новый тип и не может менять значение существующего имени типа.

Простейшая форма псевдонима эквивалентна typedef механизму из c++ 03:

Псевдонимы также работают с указателями на функции, но гораздо удобнее для чтения, чем эквивалентное определение типа:

Ограничением typedef механизма является то, что оно не работает с шаблонами. Напротив, синтаксис псевдонима типа в C ++11 позволяет создавать шаблоны псевдонимов:

Пример

Определения типов

Объявления typedef можно использовать для создания более коротких или более понятных имен для типов, уже определенных в языке или объявленных пользователем. Имена typedef позволяют инкапсулировать детали реализации, которые могут измениться.

При объявлении в локальной области идентификатора с тем же именем, что и имя typedef, или при объявлении члена структуры либо объединения в той же области или во внутренней области обязательно должен указываться спецификатор типа. Пример:

Чтобы повторно использовать имя FlagType для идентификатора, члена структуры или члена объединения, необходимо указать тип:

поскольку FlagType воспринимается как часть типа, а не как заново объявляемый идентификатор. Это объявление недопустимо, как и

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

Примеры

Использование typedef объявлений состоит в том, чтобы сделать объявления более однородными и компактными. Пример:

Чтобы использовать typedef для указания фундаментальных и производных типов в одном объявлении, можно разделить деклараторы запятыми. Пример:

В следующем примере задан тип DRAWF для функции, не возвращающей никакого значения и принимающей два аргумента int.

После оператора выше typedef объявление

будет эквивалентно следующему:

typedef часто объединяется с struct для объявления и именования определяемых пользователем типов:

Повторное объявление определений типов

typedef Объявление можно использовать для повторного объявления того же имени для ссылки на один и тот же тип. Пример:

typedef Невозможно переопределить имя, которое ранее было объявлено как другой тип. Таким образом, если file2. H содержит

компилятор выдает ошибку из-за попытки повторного объявления имени CHAR как имени другого типа. Это правило распространяется также на конструкции, подобные следующим:

определения типов в C++ и C

Преимущество такого объявления заключает в том, что можно выполнять объявления

В C++ разница между typedef именами и реальными типами (объявленными с class struct union enum ключевыми словами,, и) более Разна. Хотя методика C объявления структуры без имени в typedef инструкции по-прежнему работает, она не предоставляет преимуществ для нотаций, как это делается в c.

Имя (синоним) не может использоваться после class struct union префикса, или.

Имя не может использоваться в качестве имени конструктора или деструктора в объявлении класса.

Таким образом, этот синтаксис не предоставляет механизм наследования, создания или удаления.

Источник

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 не

Источник

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

Такое количество ключевых слов введет в ступор любого неподготовленного разработчика. Но на 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 не будет опубликован. Обязательные поля помечены *