Объявления
Объявления — это способ введения (или повторного введения) имён и/или сущностей в программу C++. Не каждое объявление действительно что-либо объявляет, и разные виды сущностей объявляются по-разному. Определение — это объявление, которое содержит полное описание вводимой сущности.
Объявление — это один из следующих вариантов:
- Определение функции
- Объявление шаблона (включая частичную специализацию шаблона)
- Явная инстанциация шаблона
- Явная специализация шаблона
- Определение пространства имён
- Спецификация связывания
| (начиная с C++11) |
- Пустое объявление (
;) - Объявление функции без decl-specifier-seq :
attr (необязательно) declarator ; |
| attr | - | (начиная с C++11) последовательность из любого количества атрибутов |
| declarator | - | декларатор функции |
- block-declaration (объявление, которое может находиться внутри блока); оно, в свою очередь, может быть одним из следующих вариантов:
| (начиная с C++11) |
| (начиная с C++20) | |
| (начиная с C++11) |
- простое объявление
Обратите внимание, что declaration — это синтаксическая конструкция, тогда как объявление — термин C++. Объявлением может быть не только declaration, но и спецификатор класса, спецификатор перечисления, параметр функции и т. д. Для имени объявление, которое вводит это имя, и объявление обозначаемой им сущности не всегда совпадают:
template <class T>
struct A {};
В этом примере объявлением, вводящим имя A, является спецификатор класса, тогда как объявлением, вводящим сущность — шаблон класса A, является окружающее его объявление шаблона.
Простое объявление
Простое объявление — это оператор, который вводит, создаёт и, при необходимости, инициализирует один или несколько идентификаторов, обычно переменных.
decl-specifier-seq init-declarator-list (необязательно) ; | (1) | |
attr decl-specifier-seq init-declarator-list (необязательно) ; | (2) | (начиная с C++11) |
| decl-specifier-seq | - | последовательность спецификаторов |
| init-declarator-list | - | список init-declarator , разделённых запятыми (см. ниже) |
| attr | - | последовательность из любого количества атрибутов |
init-declarator-list можно опустить только при объявлении именованного класса или именованного перечисления.
| Объявление структурированной привязки также является простым объявлением. | (начиная с C++17) |
Синтаксис init-declarator определяется следующим образом:
| declarator initializer | (1) | |
| declarator requires-clause (необязательно) contract-specs (необязательно) | (2) |
| declarator | - | декларатор |
| initializer | - | инициализатор |
| requires-clause | - | (начиная с C++20) предложение requires |
| contract-specs | - | (начиная с C++26) список спецификаторов контрактов функции |
| requires-clause может присутствовать, только если declarator объявляет шаблонную функцию. | (начиная с C++20) |
| contract-specs может присутствовать, только если declarator объявляет функцию или шаблон функции. | (начиная с C++26) |
Спецификаторы
Спецификаторы объявления (decl-specifier-seq) — это последовательность следующих спецификаторов, разделённых пробелами и расположенных в произвольном порядке:
- спецификатор
typedef. Если он присутствует, всё объявление является объявлением typedef, и каждый декларатор вводит новое имя типа, а не объект или функцию. - спецификаторы функций (
inline,virtual,explicit); допускаются только в объявлениях функций.
| (начиная с C++17) |
- спецификатор
friend, допустимый в объявлениях классов и функций.
| (начиная с C++11) |
| (начиная с C++20) |
-
Спецификатор класса хранения (
register, (до C++17)static,thread_local, (начиная с C++11)extern,mutable). Допускается только один спецификатор класса хранения, за исключением случая, когда thread_local может использоваться вместе с extern или static(начиная с C++11). - Спецификаторы типа (type-specifier-seq) — последовательность спецификаторов, задающих тип. Тип каждой сущности, вводимой объявлением, является этим типом, возможно изменённым декларатором (см. ниже). Эта последовательность спецификаторов также используется в type-id. В type-specifier-seq входят только следующие спецификаторы, расположенные в произвольном порядке:
- спецификатор класса
- спецификатор перечисления
- простой спецификатор типа
| (начиная с C++11) | |
| (начиная с C++26) |
- ранее объявленное имя класса (возможно, квалифицированное)
- ранее объявленное имя перечисления (возможно, квалифицированное)
- ранее объявленное имя typedef или псевдоним типа(начиная с C++11) (возможно, квалифицированное)
- имя шаблона с аргументами шаблона (возможно, квалифицированное, возможно, с использованием дизамбигуатора template)
| (начиная с C++17) |
-
- уточнённый спецификатор типа
- ключевое слово
class,structилиunion, за которым следует идентификатор (возможно, квалифицированный), ранее определённый как имя класса. - ключевое слово
class,structилиunion, за которым следует имя шаблона с аргументами шаблона (возможно, квалифицированное, возможно, с использованием template дизамбигуатора), ранее определённое как имя шаблона класса. - ключевое слово
enum, за которым следует идентификатор (возможно, квалифицированный), ранее объявленный как имя перечисления. - спецификатор typename
- cv-квалификатор
- в decl-specifier-seq допускается только один спецификатор типа, за исключением следующих случаев:
- const можно сочетать с любым спецификатором типа, кроме самого себя.
- volatile можно сочетать с любым спецификатором типа, кроме самого себя.
- signed или unsigned можно сочетать с char, long, short или int.
- short или long можно сочетать с int.
- long можно сочетать с double.
| (начиная с C++11) |
Атрибуты могут находиться в decl-specifier-seq; в этом случае они применяются к типу, заданному предшествующими спецификаторами.
Повторение любого спецификатора в decl-specifier-seq, например conststaticconst или virtualinlinevirtual, является ошибкой, за исключением того, что long может встречаться дважды(начиная с C++11).
Деклараторы
Каждый init-declarator в init-declarator-list S D1, D2, D3; обрабатывается так, как если бы он был отдельным объявлением с теми же спецификаторами: S D1; S D2; S D3;.
Каждый декларатор вводит ровно один объект, ссылку, функцию или (в случае объявлений typedef) псевдоним типа; его тип задаётся decl-specifier-seq и может дополнительно модифицироваться символами, такими как & (ссылка на), [] (массив из) или () (функция, возвращающая), в деклараторе. Эти операторы можно применять рекурсивно, как показано ниже.
declarator — это один из следующих вариантов:
| unqualified-id attr (необязательно) | (1) | |
| qualified-id attr (необязательно) | (2) | |
... identifier attr (необязательно) | (3) | (начиная с C++11) |
* attr (необязательно) cv (необязательно) declarator | (4) | |
nested-name-specifier * attr (необязательно) cv (необязательно) declarator | (5) | |
& attr (необязательно) declarator | (6) | |
&& attr (необязательно) declarator | (7) | (начиная с C++11) |
noptr-declarator [ constexpr (необязательно) ] attr (необязательно) | (8) | |
noptr-declarator ( parameter-list ) cv (необязательно) ref (необязательно) except (необязательно) attr (необязательно) | (9) |
S * D; объявляет D указателем на тип, заданный decl-specifier-seq S.S C::* D; объявляет D указателем на член C типа, заданного decl-specifier-seq S. nested-name-specifier — это последовательность имён и операторов разрешения области видимости ::
S & D; объявляет D ссылкой на lvalue для типа, заданного decl-specifier-seq S.S && D; объявляет D ссылкой на rvalue для типа, заданного decl-specifier-seq S.| Во всех случаях attr — это необязательная последовательность атрибутов. Если она находится непосредственно после идентификатора, то применяется к объявляемому объекту. | (начиная с C++11) |
cv — это последовательность квалификаторов const и volatile, каждый из которых может встречаться в ней не более одного раза.
В соответствии с правилами поиска имён, если в одной области видимости объявлены тип и другая сущность с одинаковым именем, имя типа скрывается. Обратите внимание, что тип всё ещё можно найти с помощью уточнённого спецификатора типа.
class Foo {};
void Foo();
// Foo obj;// Error: 'Foo' is not a type
class Foo obj;// OK
Примечания
Если block-declaration находится внутри блока, а идентификатор, введённый объявлением, уже был объявлен во внешнем блоке, внешнее объявление скрывается до конца этого блока.
Если объявление вводит переменную с автоматической длительностью хранения, она инициализируется при выполнении оператора объявления. Все автоматические переменные, объявленные в блоке, уничтожаются при выходе из блока (независимо от способа выхода: через исключение, goto или по достижении конца блока) в порядке, обратном порядку их инициализации.
Пример
Примечание: этот пример демонстрирует, как некоторые сложные объявления разбираются в соответствии с грамматикой языка. Среди других популярных мнемонических правил: спиральное правило, чтение изнутри наружу и отражение использования в объявлении. Также доступен автоматический анализатор на сайте https://cdecl.org.
#include <type_traits>
struct S
{
int member;
// decl-specifier-seq is "int"
// declarator is "member"
} obj, *pObj(&obj);
// decl-specifier-seq is "struct S { int member; }"
// declarator "obj" declares an object of type S
// declarator "*pObj" declares a pointer to S,
// and initializer "(&obj)" initializes it
int i = 1, *p = nullptr, f(), (*pf)(double);
// decl-specifier-seq is "int"
// declarator "i" declares a variable of type int,
// and initializer "= 1" initializes it
// declarator "*p" declares a variable of type int*,
// and initializer "= nullptr" initializes it
// declarator "f()" declares (but doesn't define)
// a function taking no arguments and returning int
// declarator "(*pf)(double)" declares a pointer to function
// taking double and returning int
int (*(*var1)(double))[3] = nullptr;
// decl-specifier-seq is "int"
// declarator is "(*(*var1)(double))[3]"
// initializer is "= nullptr"
// 1. declarator "(*(*var1)(double))[3]" is an array declarator:
// Type declared is: "(*(*var1)(double))" array of 3 elements
// 2. declarator "(*(*var1)(double))" is a pointer declarator:
// Type declared is: "(*var1)(double)" pointer to array of 3 elements
// 3. declarator "(*var1)(double)" is a function declarator:
// Type declared is: "(*var1)" function taking "(double)",
// returning pointer to array of 3 elements.
// 4. declarator "(*var1)" is a pointer declarator:
// Type declared is: "var1" pointer to function taking "(double)",
// returning pointer to array of 3 elements.
// 5. declarator "var1" is an identifier.
// This declaration declares the object var1 of type "pointer to function
// taking double and returning pointer to array of 3 elements of type int"
// The initializer "= nullptr" provides the initial value of this pointer.
// C++11 alternative syntax:
auto (*var2)(double) -> int (*)[3] = nullptr;
// decl-specifier-seq is "auto"
// declarator is "(*var2)(double) -> int (*)[3]"
// initializer is "= nullptr"
// 1. declarator "(*var2)(double) -> int (*)[3]" is a function declarator:
// Type declared is: "(*var2)" function taking "(double)", returning "int (*)[3]"
// ...
int main()
{
static_assert(std::is_same_v<decltype(var1), decltype(var2)>);
}
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применён к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 482 | C++98 | деклараторы повторных объявлений не могли быть квалифицированными | квалифицированные деклараторы разрешены |
| CWG 569 | C++98 | одна отдельная точка с запятой не являлась допустимым объявлением | это пустое объявление, не имеющее эффекта |
| CWG 1830 | C++98 | повторение спецификатора функции в decl-specifier-seq было разрешено | повторение запрещено |
См. также
| Документация по C о объявлениях |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/declarations