C++. Бархатный путь. Часть 2 - Выражение вызова (обращения к конструктору)

ОГЛАВЛЕНИЕ

 

Выражение вызова (обращения к конструктору)

Часто вообще невозможно сказать что-либо определённое по поводу того, что обеспечивает передачу управления конструктору - так называемое выражение вызова (или обращения к конструктору), либо выражение, которое используется для преобразования типа (постфиксный вариант выражения преобразования типа). Соответствующая БНФ уже приводилась ранее. Напомним её:

ПосфиксноеВыражение ::= ИмяПростогоТипа ([СписокВыражений])

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

(ComplexType) 25;
/* В этом случае мы имеем дело с выражением преобразования. При
вычислении его значения производится обращение к конструктору
ComplexType(double). */
(float) 25;
/* Здесь нет никаких обращений к конструктору. Базовый тип float
классом не является и конструкторов не имеет. Перед нами оператор,
состоящий из выражения приведения (целочисленное значение приводится
к типу float). */
float x = float(25);
/* В этом случае для определения значения выражения явного
преобразования типа, записанного в функциональной форме, также не
требуется никаких обращений к конструктору. */
ComplexType (25);
/* Казалось бы, здесь мы также имеем дело с функциональной формой
выражения явного преобразования типа - оператором на основе постфиксного
выражения. Для вычисления значения этого выражения необходимо обратиться
к конструктору ComplexType(double). */

На последнее предложение следует обратить особое внимание. Дело в том, что аналогичный оператор на основе постфиксного выражения для основных типов языка C++ воспринимается транслятором как ошибка:

float (25);
/* Это некорректный оператор! Для любого из основных типов C++
здесь будет зафиксирована ошибка. */

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

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

Таким образом, оператор

float (25);

(и ему подобные операторы для основных типов) представляется транслятору объявлением с пропущенным описателем и альтернативной формой инициализатора. Чем-то, напоминающим следующую конструкцию:

float = 25;

при разборе подобного предложения транслятор, естественно, не находит ожидаемого описателя и сообщает об ошибке в объявлении.

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