C++. Бархатный путь. Часть 2 - Условное выражение на основе выражения явного преобразования
ОГЛАВЛЕНИЕ
Условное выражение на основе выражения явного преобразования
В C++ можно построить условное выражение на основе выражения явного преобразования к одному из основных типов. Основные типы имеют простую структуру, а потому значение такого выражения определить очень просто:
if (char(charVal)) {/*…*/}
if (float(5)) {/*…*/}
if ((int)3.14){/*…*/}
if (double (0)){/*…*/}
Включение в условия условных операторов выражений, вычисление значений которых приводит к передаче управления конструкторам, требует дополнительных усилий со стороны прораммиста. У порождаемых конструкторами объектов сложная структура и неизвестные транслятору способы определения значений, представляемых такими объектами. Кроме того, определённый в языке набор операций приспособен исключительно для работы со значениями основных типов. Транслятор не имеет абсолютно никакого представления о том, каким образом следует, например, сравнивать значения того же самого ComplexType.
Однако, C++ располагает специальными средствами, которые позволяют создавать иллюзию условных выражений с объектами-операндами производных типов. Чуть позже мы рассмотрим так называемые операторные функции (или перегруженные операции), с помощью которых можно будет всё-таки сформулировать условия, подобные тем, которые формулируются относительно значений основных типов:
if (ComplexType()){/*…*/}
if (ComplexType() > 10 && ComplexType() <= 25 ){/*…*/}
Правда, в данном контексте за символами операций сравнения и даже за выражением "явного вызова конструктора" скрываются так называемые сокращённые формы вызова операторных функций, а не обычные операции C++.
А какое условие можно сформулировать в терминах операций, пригодных для работы исключительно со значениями основных типов по поводу значения безымянного объекта производного типа, который, к тому же и погибает сразу же после своего рождения?
В C++ невозможно сформулировать условие относительно сложного объекта "в целом", используя при этом стандартный набор операций, но легко можно определить значения данных-членов этого объекта. Для этого используется операция выбора компонента:
if (ComplexType().real && !ComplexType().imag){/*…*/}
Вот мы и узнали кое-что о свойствах объекта. Правда, объектов в условии целых два. У первого безымянного объекта мы поинтересовались значением данного-члена real, после чего он благополучно отошёл "в мир иной", у второго объекта выяснили значение данного-члена imag.
Выражения вызова функций типа void так же недопустимы в контексте условия, поскольку функции void "возвращают" пустые значения. Например,
void MyProc();
:::::
void MyProc() {/*…*/}
:::::
if (MyProc()) {/*…*/} /* Здесь ошибка */
for ( ; MyProc(); ) {/*…*/} /* Здесь ошибка */
if (ComplexType()){/*…*/} /* Это тоже ошибка */
Выражение явного преобразования типа можно расположить справа от символа операции присвоения в операторе присвоения.
ComplexType MyVal = ComplexType ();
ComplexType MyVal = ComplexType (25);
ComplexType MyVal = (ComplexType) 25;
И опять перед нами так называемый явный вызов конструктора. Но, как сказано в справочном руководстве по C++, "явный вызов конструктора означает не то же самое, что использование того же синтаксиса для обычной функции-члена". Конструктор вызывается не для объекта класса, как другие функции-члены, а для области памяти. Для её преобразования ("превращения") в объект класса.
На самом деле, здесь конструктор вызывается дважды. В первый раз при создании переменной MyVal, второй - в ходе выполнения операции явного преобразования значения, возможно, что пустого. При этом создаётся временный безымянный объект, значения данных-членов которого присваиваются переменной MyVal. Нам ещё предстоит выяснить, как работает операция присвоения на множестве производных типов, в частности, в сочетании с выражением явного преобразования типа, которое приводит к вызову конструктора. И если можно ещё как-то представить пустое значение, которое используется для начальной инициализации данных-членов вновь создаваемого объекта, то присвоение пустого значения леводопустимому выражению в принципе невозможно. Поэтому выражение вызова функции с void спецификатором в операторе присвоения недопустимо:
int MyVal = MyProc(); /* Ошибка */
int MyVal = (void)MyProc(); /* Ошибка */
И ещё одно сравнение между конструктором и void-процедурой. Поскольку тип void - это всё же тип, мы можем объявить указатель на void-процедуру.
void MyFunction (void);
:::::
void (*MyFunctionPointer) (void);
Указатель на функцию можно настроить на адрес конкретной функции. Для этого существует операция взятия адреса:
MyFunctionPointer = MyFunction; /* Можно так. */
MyFunctionPointer = &MyFunction; /* А можно и так. */
С конструктором всё по-другому. Мы можем определить адрес создаваемого конструктором объекта. Всё то же выражение явного преобразования типа обеспечивает обращение к конструктору, который создаёт в памяти безымянный объект, чей адрес и определяется операцией взятия адреса:
if (&ComplexType()) {/*…*/}
Но вот объявить указатель на конструктор и определить адрес конструктора невозможно. Объявление указателя на функцию требует стандартной спецификации типа функции. Операция взятия адреса возвращает значение определённого типа. Конструктор же не обладает стандартной спецификацией, а потому невозможно определить для него указатель и определить соответствующее значение.