Если typeid не может определить тип объекта, выбрасывается исключение bad_typeid. Это происходит, например, при попытке определить тип, на который ссылается нулевой указатель:
//////////////////////////////////////////////
// BadType.cpp: Исключение bad_typeid.
//
#include <iostream.h> #include <typeinfo.h>
#pragma hdrstop
#include <condefs.h>
class Base { public:
virtual ~Base() {} };
class Derived: public Base {};
int main() {
try {
Base *bp = NULL;
cout<< "Typeid of bp: " << typeid(*bp).name() << endl;
} catch(bad_typeid) {
cout << "Bad typeid caught!"<< endl;
} return 0;
}
Операция cons't_cast имеет ту же форму, что и предыдущая:
соnst_сonst<целевой_тип>(аргумент)
Целевой тип, возвращаемый такой операцией, может быть любым и должен отличаться от типа аргумента только модификаторами const и volatile.
Вот пример инициализации динамической константной строки:
/////////////////////////////////////////////
// ConstCast.срр: Подавление модификатора const.
//
#include <string.h>
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
int main ()
cons с char *ip;
ip = new char[20];
strcpy(const_cast<char*>(ip), "New const string,");
cout << ip << end1; delete [] ip;
return 0;
Операция динамического приведения типа
dynamic сast<целевой_тип>(аргумент)
не имеет аналогов среди операций, выполняемых .с применением “классической” нотации приведения. Операция и проверка ее корректности при известных условиях происходит во время выполнения программы.

Динамическое приведение типа опирается на механизм RTTI, поэтому необходимо установить флажок Enable RTTI в диалоге Project Options (страница C++). Если этот флажок сброшен, программа компилироваться не будет.
Целевой тип операции должен быть типом указателя, ссылки или void*. Если целевой тип — тип указателя, то аргументом должен быть указатель на объект класса; если целевой тип — ссылка, то аргумент должен также быть соответствующей ссылкой. Если целевым типом является void*, то аргумент также должен быть указателем, а результатом операции будет указатель, с помощью которого можно обратиться к любому элементу “самого производного” класса иерархии, который сам не может быть базовым ни для какого другого класса.
Приведение от производного класса к базовому разрешается на этапе компиляции. Преобразования от базового класса к производному, либо перекрестные преобразования на некоторой иерархии, происходят во время выполнения программы. Операция нисходящего приведения типа допустима только в случае, если базовый класс (класс аргумента) является полиморфным.
При попытке произвести некорректное преобразование операция возвращает нуль, если целевой_тип — указатель. Если ссылка, операция выбрасывает исключение типа bad_cast.
С помощью операции dynamic_cast можно выполнять нисходящее приведение виртуального базового класса, что невозможно сделать с применением обычной нотации приведений, при условии, что базовый класс является полиморфным и преобразование разрешается однозначно.
Ниже показаны две программы, демонстрирующие динамическое приведение типа. В первой из них для контроля успешности преобразований используются исключения, во второй — проверка на равенство результата нулю.
Листинг 13.3. Нисходящее и перекрестное приведение типа

Для получения информации о типе во время выполнения программы применяется операция typeid:
typeid(имя_ типа) typeid(выражение)
Ее операндом является либо имя типа, либо выражение, оцениваемое как некоторый тип. Операция возвращает константную ссылку на объект класса type_info, объявленный в заголовке typeinfo.h.
Если операция не может определить тип своего операнда, она выбрасывает исключение типа bad_typeid.
Следует помнить, что RTTI в собственном смысле, как динамическое распознавание типа, работает только с полиморфными типами, т. е. классами, имеющими хотя бы одну виртуальную функцию. Если применить операцию typeid к обычному типу, идентификация типа будет произведена статически, т. е. при компиляции.
Синтаксис данной формы операции приведения таков:
reinterpret_cast<Целевой_тиn> (аргумент)
Такую операцию можно применить для того, чтобы изменить интерпретацию объекта без действительного преобразования данных.
Целевой_тип может быть типом ссылки, указателя, целым, перечислимым или вещественным типом.
Если целевой_тип — тип указателя или ссылки, то аргумент может быть указателем или ссылкой, а также числовой (вещественной, целой, перечислимой) переменной; когда целевым типом является числовой тип, то операнд может быть указателем или ссылкой.
Операция возвращает значение целевого типа.
Возможно, например, явное преобразование указателя в целый тип, равно как и обратная операция. Можно приводить указатель на функцию одного •типа к указателю на функцию другого типа или на некоторый объект, при условии, что он (указатель на объект) имеет достаточную разрядность.
Вот пример преобразования указателя в целое и наоборот:
////////////////////////////////////////////////
// Reinterpret.срр: Демонстрация reinterpret_cast
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
int main () {
int i = 7;
int *ip = Si;
int temp = reinterpret cast<int>(ip);
cout.setf(ios::showbase) ;
cout << "Pointer value is"<< ip << end1;
cout << "Representation of a pointer as int is " << hex << temp << endl;
cout << "Convert it back and dereference:"<<*reinterpret_cast<int*>(temp) << endl;
return 0;
}
Эта программа выводит:
Pointer value is 0065FEOO
Representation of a pointer as int is Ox65fe00
Convert it back and dereference: 0х7
Все это можно проделать, разумеется, и с помощью обычных операций приведения, однако последние мало надежны. Тут при опечатках могут происходить совершенно дикие преобразования, и компилятор не выдает даже предупреждающих сообщений. Специальные же операции имеют более корректный вид и явно показывают, что вы делаете.
В этом разделе описан синтаксис и даются примеры использования RTTI.
Аббревиатура RTTI означает RunTime Type Identification, т. е. “Идентификация типа времени выполнения”. Это механизм, позволяющий определить тип объекта во время выполнения программы, что очень полезно в иерархии типов, где указатель или ссылка базового класса может ссылаться на представитель любого производного класса. Полиморфные механизмы, конечно, хороши, но выглядят снаружи подобно “черному ящику”. Вы вызываете виртуальные методы, но не знаете, к чему, собственно, они применяются. Иногда требуется точно знать тип объекта. Если же можно с уверенностью идентифицировать типы, открывается возможность безопасного их приведения.
В этой главе рассматриваются RTTI и усовершенствованные операции приведения типа C++.
Стандарт ANSI определяет специальный синтаксис операций приведения типа, позволяющий программисту воспользоваться преимуществами RTTI и, кроме того, указать точно, что он хочет получить в результате таких операций. Новых операций приведения четыре: dynamic_cast, static cast, reinterpret cast и const_cast.
Здесь нужно вспомнить, для чего вообще может служить приведение типа. Можно назвать следующие случаи:
Чтобы изменить действительное представление данных либо поведение объекта, на который ссылается некоторый указатель. Простейшее приведение такого рода — преобразование целого типа в вещественный.
Чтобы изменить лишь интерпретацию компилятором некоторых данных, не меняя их действительного (физического) представления. Таково, например, приведение типа int к типу unsigned и наоборот.
Чтобы снять ограничения на возможные манипуляции с объектом, накладываемые модификатором const.
Эти три случая соответствуют, говоря, может быть, несколько упрощенно, трем последним из перечисленных в начале раздела операций. Операция же dynamic cast позволяет безопасно приводить типы в различных полиморфных иерархиях классов, в том числе с виртуальными базовыми классами.
Мы начнем с более простых и традиционных приведений.
Операция статического приведения типа
static саst<целевой тип> (аргумент)
может выполнять преобразования между числовыми типами, а также между указателями либо ссылками на объекты классов, находящихся в иерархическом отношении (если оно однозначно и базовый класс — не виртуальный). Операция реализуется во время компиляции.
Преобразования числовых типов происходят точно так же, как в случае обычной нотации приведений. Приведение указателей и ссылок возможно как от производного класса к базовому (тут все достаточно просто), так и от базового к производному (нисходящее приведение типа). Конечно, следует помнить, что во многих случаях нисходящее приведение указателя базового типа не будет безопасным, если только он не ссылается в действительности на представитель производного класса.
Если некоторый указатель может быть приведен к типу Т*, то объект этого типа может быть приведен к типу Т&.
Объект или значение могут быть приведены к объекту некоторого класса, если в данном классе объявлен соответствующий конструктор или имеется подходящая операция преобразования. Этот момент продемонстрирован в приведенной ниже программе.
Листинг 13.2. Нисходящее приведение указателей и ссылок
//////////////////////////////////////////////////////
// StatCast.срр: Статическое нисходящее приведение типа.
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
class A {} ;
class B: public A { public:
int i;
B(int ii): i(ii) {}
B(A&): i(11) {
cout << "Derived conversion constructor... ";
} };
int main() {
В b(22), *pb = &b;
A Sra = static cast<A&>(b); // Ссылка на b как
// базовый объект.
А *ра = static_cast<A*>(pb); // Указатель на b как
// базовый объект.
cout << "Derived object: " << b.i << endl;
cout << "Downcasting pointer to pointer: "
<< static_cast<B*>(pa)->i << endl;
// Приведение
// указателей.
cout <<"Downcasting referense to referense: "
<< static cast<B&>(га).i<< endl;
// Приведение
// к ссылке.
cout << "Downcasting reference to object: ";
cout << static cast<B>(ra).i<< endl;
// Приведение
// к объекту.
return 0;
}
Вот что выводит этот код:
Derived object: 22
Downcasting pointer to pointer: 22
Downcasting referense to referense: 22
Downcasting reference to object: Derived conversion
constructor... 11
Как видите, приведение ссылки базового класса к ссылке производного дает ссылку на первоначальный объект производного класса (b), в то время как преобразование той же ссылки в представитель производного класса конструирует новый (временный) объект.
Класс type_info объявлен следующим образом:
class _TIDIST _rtti type_info {
public:
tpid * tpp;
private:
cdecl type_info(const type info FAR &);
type info & cdecl operator=(const type_info _FAR &);
public:
virtual _cdecl ~type_info() ;
bool cdecl operator==(const type info FAR &) const;
bool cdecl operator!=(const type info FAR &) const;
bool _cdecl before(const type_info _FAR &) const;
const char _FAR *_cdecl name() const;
protected:
cdecl type_info(tpid * tpp) { tpp = tpp; } };

Ключевое слово _rtti перед именем класса гарантирует, что информация о типе для него будет генерироваться вне зависимости от состояния флажка Enable RTTI на странице C++ диалога Project Options (ему соответствует ключ компилятора -rt).
Открытые элементы класса представлены операциями сравнения на равенство и неравенство, а также функциями name () и before (). Первая возвращает указатель на символьную строку с именем типа. Вторая возвращает true, если класс ее объекта является базовым по отношению к классу аргумента.
Вот пример с использованием операции typeid и класса type_info:
Листинг 13.1. Операция typeid
//////////////////////////////////////////////////////
// Typeinfo.срр: Операция typeid.
//
#include <typeinfo.h>
#include <iostream.h>
#include <string.h>
#pragma hdrstop
#include <condefs.h>
class Base { // Базовый класс.
public:
virtual ~Base (){} };
class Derived: public Base { // Производный класс.
char *str;
public:
Derived(const char *s) {
str = new char[strien(s)+1];
strcpy(str, s);
}
~Derived() { delete [] str;}
const char *Get() ( return str;}};
int main() {
Derived d("Derived class' string.");
Base &bRef = d; // Базовая ссылка на производный объект.
cout << "Typeinfo of bRef: " << typeid(bRef).name() << end1;
if (typeid(bRef) == typeid(Derived))
cout << "Contents of bRef: "<< ((Derived 6)bRef).Get() << endl;
else
cout << "Cannot cast safely bRef to Derived." << endl;

Введение в визуальное программирование
Создание визуальных компонентов
В этой заключительной, очень небольшой части книги мы обсудим некоторые аспекты “визуального программирования”, как обычно называют этот род деятельности. Другое название — Rapid Application Development (RAD), “ускоренная разработка приложений”. В отличие от описаний стандартного языка C/C++ в первых двух частях книги изложение будет носить совершенно неформальный характер. Мы будем ориентироваться в основном на разбор конкретных примеров. Я настоятельно рекомендую читателю побольше экспериментировать, изучать материалы оперативного справочника (кстати, сейчас существует, кажется. Help для C++Builder'a на русском языке) и вообще, так сказать, творчески подходить к делу. Визуальное программирование, при всей его кажущейся легкости, для рядового программиста оказывается своего рода искусством с неизбежным атрибутом в виде метода проб и ошибок, поскольку C++Builder и его библиотеки визуальных компонентов представляют собой очень большую и сложную систему.
В заключение главы мы покажем еще один пример, который не продемонстрирует ничего особенно нового, но послужит основой для разработки нашего собственного компонента в следующей главе. Пример выводит на форму “бегущую строку”.
Проектирование формы сводится к установке подходящего (небольшого) ее размера и размещению всего трех командных кнопок с надписями “Старт”, “Стоп” и “Выход”. Вы можете руководствоваться рис. 14.5, на котором показана запущенная программа.
Вы, конечно, уже знакомы с визуальным программированием в С+ + Builder, когда на экране перед вами находится фирма, на нее накладываются различные компоненты, реализующие элементы управления графического интерфейса, а затем пишется код для событий этих компонентов, использующий свойства последних. Возможно, вы работали раньше с Visual Basic или даже с Delphi. Все эти системы внешне очень похожи друг на друга, a Delphi — вообще двойник C++Builder'a и опирается на ту же самую библиотеку компонентов.
Определение класса формы создается в h-файле с тем же именем, что и имя модуля. Открыть в редакторе этот файл можно, выбрав в контекстном меню пункт Open Source/Header File. Как видите, класс содержит указатели на объекты размещенных на форме компонентов, а также объявления обработчиков событий. Все элементы класса формы (кроме открытого конструктора) объявлены в разделе с меткой _published (опубликованные). Объявление элемента класса в этом разделе эквивалентно объявлению в разделе public за исключением того, что опубликованные свойства и методы доступны в режиме проектирования через инспектор объектов. Например, поле выбранного события в правой колонке инспектора представляет собой комбинированный выпадающий список. При нажатии на стрелку отображаются доступные обработчики события с подходящим набором параметров. Если переместить, например, обработчик ButtonlClick из раздела _published в раздел public, в этих списках вы его больше не увидите, однако вы можете ввести его имя вручную или, скажем, установить обработчик программно.
Как вы, без сомнения, также заметили, все функции-элементы класса формы объявлены со спецификацией _fastcall. Этот спецификатор протокола вызова описывался в 4-й главе. Он означает, что аргументы при вызове такой функции должны по возможности передаваться в регистрах процессора.
В библиотеке VCL применяется исключительно соглашение о вызове _fastcall. Функции, написанные вами, не обязаны иметь эту спецификацию, однако если функция является методом формы или компонента, разумным будет объявить ее именно так. Во-первых, этот вызов действительно быстрее вызова С или stdcall, а во-вторых, просто ради единообразия.
После этого нужно ввести код программы. Помимо кода обработчиков OnClick для кнопок и OnPaint для формы, вам потребуется написать:
код тела конструктора формы;
деструктор формы;
объявления полей bm, started, position и -interval класса формы;
методы Setup () и Loop ().
Код программы показывает следующий листинг.
Листинг 14.2. Файлы RunningU.h и RunningU.cpp
//---------------------------------------
// RunningU.h
//---------------------------------------
#ifndefRunningUII
#define RunninqUH
//---------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <:Forms.hpp>
#include <ExtCtrls.hpp>
//---------------------------------------
class TFormI : public TForm {
_published: // IDE-managed Components
TButton *Buttonl;
TButton *Button2;
TButton *Button3;
void _fastcall Button3Click(TObject *Sender);
void _fastcail Button1Click(TObject *Sender);
void _fastcall Button2Click(TObject *Sender);
void _fastcall FormPaint(TObject *Senders-private:
// User declarations
Graphics::TBitmap *bm;
inL position;
bool started;
void fastcall Setup();
void _fastcall Loop();
public: // User declarations
int interval;
_fastcall TFormI(TComponent* Owner);
_fastcail ~TForml<);
};
//---------------------------------------
extern PACKAGE TFormI *Forml;
//---------------------------------------
#endif
//---------------------------------------
// RunningU.cpp: Исходный модуль программы
//с "бегущей строкой".
//---------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <sys\timeb-:h>
#include "RunningU.h"
//---------------------------------------
#pragma package (smart_init)
#pragma resource "*.dfm"
TFormI *Forml;
char tent[80] = "Тестирование довольно длинной бегущей
строки...";
//---------------------------------------
// Конструктор формы - выделяет и инициализирует
// битовую матрицу.

Под компонентами в C++Builder понимаются объекты или классы объектов, являющиеся, в некотором смысле, объектами “реального мира”. Вы непосредственно видите их на экране, их можно передвигать мышью, они реагируют на нажатие ее кнопок и т. д. Компоненты VCL инкапсулированы в классах языка Object Pascal, однако вполне возможно и написание компонентов на расширенном варианте C++, реализуемом в C++Builder.
Компоненты календаря и кнопки прокрутки, которые мы взяли для вышеприведенного примера, включены в палитру компонентов именно в качестве образцов такого рода. На самом деле стандартный календарь из VCL со страницы Win32 уже имеет все то, что мы реализовали в своем примере.
Чтобы можно было подключать библиотеку VCL к программам на C++, она сопровождается заголовочными файлами C++, моделирующими разделы интерфейса модулей языка Pascal. Эти заголовки содержат “параллельные” определения классов VCL. Заголовки VCL имеют по большей части расширение .hpp.
VCL расшифровывается как “библиотека визуальных компонентов”. Однако среди ее компонентов попадаются и не визуальные. Не визуальным компонентом является, например, таймер (класс TTimer). Компоненты стандартных диалогов тоже, как это ни странно, не визуальны.
Основным строительным элементом визуального программирования является компонент. В свою очередь, для компонентов, в отличие от обыкновенных объектов C++, характерно наличие свойств и событий.
Поле — это просто другое название для элемента данных класса. Соответственно метод — синоним для функции-элемента класса. Для классов компонентов в C++Builder применяются именно эти термины.
Начнем с очень простого примера. Создайте новое приложение (File New Application в главном меню или значок Application в диалоге New Items). На экране появится пустая форма. Уменьшите ее размер и, руководствуясь рис. 14.1, разместите на ней следующие компоненты: календарь (CCalendar) и кнопку прокрутки (CSpinButton) со страницы Samples палитры компонентов, а также командную кнопку и две метки со страницы Standard. (Я не буду приводить детальных пошаговых инструкций, поскольку вы и так наверняка умеете все это делать, а если не умеете, то полезно будет разобраться самому, да это и не сложно.)

Рис. 14.1 Форма программы в режиме проектирования
Для дальнейшей работы вам потребуется инспектор объектов (если его нет на экране, откройте его через меню View). Чтобы проще было писать
код, назовите для краткости календарь Са1, а кнопку прокрутки — Spin (т. е. измените в инспекторе свойство Name этих компонентов).
Введите надписи (свойство Caption) для командной кнопки и второй метки (той. что справа, у меня она называлась Label2) — соответственно “Выход” и “Месяц”. Для первой метки (внизу) установите желаемый размер и, если хотите, гарнитуру шрифта, чтобы текст легко читался и выглядел прилично.
На этом этап визуального проектирования нашего приложения закончен. (Сейчас самое время сохранить проект, присвоив имена модулю формы, который по умолчанию называется Unitl.cpp, и главному модулю Ргоjectl.cpp, по имени которого будет назван весь проект и конечный исполняемый файл.) Теперь нужно написать код, который придаст существованию расположенных на форме компонентов какую-то осмысленность.
Этот простой проект содержит следующие основные файлы:
Ргор.Ьрг — файл проекта;
Ргор.срр — главный исходный модуль, с ним редко приходится иметь дело;
PropU.cpp — модуль исходного кода, связанного с формой;
PropU.dfm — файл визуальных ресурсов формы.
PropU.h — заголовочный файл с определением класса формы.
В проекте может быть несколько форм, и каждой из них будет соответствовать свой исходный модуль. Для каждой формы генерируется также заголовочный файл с расширением .h. Проект может включать в себя и модули исходного кода, не связанные непосредственно с какими-либо формами. Если вы создаете именно модуль (значок Unit в диалоге New Items), а не просто срр-файл, то C++Builder автоматически создаст и h-файл с тем же именем.
Откройте страницу Events инспектора объектов. Общая методика написания кода такова: вы выбираете компонент (на форме или из выпадающего списка инспектора объектов) и дважды щелкаете кнопкой мыши на нужном событии в правой колонке страницы событий инспектора. C++Builder автоматически генерирует оболочку обработчика события и переключает фокус на окно редактора кода. Текстовый курсор стоит прямо там, куда вы должны ввести свой код.
Начните с кнопки “Выход”, которая должна закрывать форму, завершая тем самым приложение. Обработчик должен вызывать метод формы Close ().
Листинг 14.1. Файлы программы Prop — PropU.h и PropU.cpp
//---------------------------------------
// PropU.h: Заголовок для PropU.срр.
//---------------------------------------
#ifndef PropUH #define PropUH
//---------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include "CCALENDR.h"
#include <Grids.hpp>
#include "CSPIN.h"
//---------------------------------------
class TFormI : public TForm
{
_published: // IDE-managed Components
TButton *Buttonl;
TCCalendar *Cal;
TLabel *Labell;
TCSpinButton *Spin;
TLabel *Label2;
void _fastcall ButtonlClick(TObject *Sender);
void_fastcall CalChange(TObject *Sender);
void _fastcall SpinDownClick(TObject *Sender);
void _fastcall SpinUpClick(TObject *Sender) ;
void _fastcall FormCreate(TObject *Sender) ;
private: // User declarations
public: // User declarations
_fastcall TFormI(TComponent* Owner) ;
};
//---------------------------------------
extern PACKAGE TFormI *Forml;
//---------------------------------------
#endif
//---------------------------------------
// PropU.cpp: Исходный модуль примера с календарем.
//---------------------------------------
#include <vcl.h>
#pragma hdrstop #include <stdio.h>
#include "PropU.h"
//---------------------------------------
#pragma package(smart_init)



Это ключевое слово обозначает операцию, выполняемую во время компиляции. Синтаксис:
_classid(имя_класса)
Стандартный C++, в отличие от Object Pascal, не может оперировать классами как таковыми. Он оперирует представителями классов, конструированными объектами. Операция _classid позволяет получить, как говорят, указатель на метакласс для специфицированного класса (класс TMetaClass).
Указатель на класс как таковой, безотносительно к его представителям, необходим, например, при регистрации класса компонента. При создании нового компонента C++Builder автоматически генерирует в его модуле такой код:
namespace Cticktape Х
void __fastcail PACKAGE ReaisterO
{
TComponentClass classes[1]=
{_classid(CTickTape)};
RegisterComponents("Samples", classes, 0);
}
}
Операция _classid редко используется непосредственно. C++Builder генерирует ее автоматически, как, например, в приведенном фрагменте кода.
Ключевое слово _closure позволяет объявить специальный указатель (замыкание) на функцию-элемент. В отличие от стандартного указателя на функцию-элемент, объявленного, например, как
void (AClass::*funcPtr)(int);
и привязанного к конкретному классу, в объявлении замыкания класс не указывается и, таким образом, оно может ссылаться на любую функцию-элемент любого класса, имеющую соответствующий список параметр ров. Объявление замыкания отличается от указателя на функцию наличием ключевого слова closure перед именем переменной:
void (_closure *aClosure) (int) ;
SomeClass *some0bj = new SomeClass;
aClosure = someObj->Func;
aClosure (1);
Замыкание комбинирует указатель на функцию-элемент с указателем на конкретный объект, и последний передается в качестве this при вызове функции через замыкание.
Типы событий в классах компонентов являются замыканиями:
typedef void fastcall ( closure *TPassCompleteEvent)
(System::TObject *Sender, bool& stop);
published:
_property TPassCompleteEvent OnPassComplete =
{ read=FOnPassComplete, write=FOnPassComplete };
Применение этого ключевого слова не ограничено визуальным программированием, однако также участвует в поддержке кода VCL.
Общее применение _declspec
Выражение вида _declspec (аргумент) является модификатором, который может применяться к функциям или переменным. Аргументы dllexport, dilimport и thread формируют модификаторы, соответствующие обычным export, _import и _thread. Разница между ними заключается в том, что обычные модификаторы должны всегда непосредственно предшествовать имени объявляемой переменной или функции, в то время как модификаторы _declspec можно помещать в любом месте объявления:
void _declspec(dllexport) f(void);// Верно.
_declspec(dllexport) void f(void);// Верно.
_export void f(void) // Ошибка: должно быть
// void export.
Другими аргументами _declspec общего применения являются:
naked: Применяется к определению функции. Подавляет генерацию кода пролога/эпилога вызова, позволяя программисту написать свой собственный код, используя встроенный ассемблер.
noreturn: Сообщает компилятору, что функция не возвращает управления вызывающей программе. Обычно, когда компилятор обнаруживает, что при некоторых условиях функция, объявленная с типом возвращаемого значения, ничего не возвращает, он выдает предупреждение. Однако если эта ситуация связана с вызовом другой функции, не возвращающей управления, то, объявив последнюю с модификатором _declspec (noreturn), можно подавить такое предупреждение. Вот пример:
_declspec(noreturn) void finish(){
...
throw "No return";
}
int RetInt(){
if(....)
return 1;
else
finish(); // Без noreturn генерировалось бы
// предупреждение.
nothrew: Соответствует спецификации исключения без аргументов. Следующие объявления эквивалентны:
__declspec(nothrow) void f();
void f() throw();
novtable: Применяется к классам, для которых никогда не будет создаваться представителей. В большинстве случаев этот модификатор подавляет генерирование vtaDie и, соответственно, кода всех функции,на которые она ссылается.
Одним из показателей квалификации программиста является то, может ли он разрабатывать свои собственные компоненты для визуальной среды программирования вроде C++Builder. Пользовательский компонент должен ничем не отличаться от “настоящего” в том смысле, что его можно будет найти в палитре компонентов, можно будет устанавливать его свойства и события в инспекторе объектов и т. п. Эта глава посвящена написанию пользовательских визуальных компонентов и некоторым сопутствующим вопросам.
Создание визуального компонента — процесс сам по себе не визуальный. Все сводится к написанию кода для свойств и методов класса компонента. Здесь вам может очень помочь ClassExplorer, автоматически или, скорее, полуавтоматически генерирующий базовый код. С двумя его диалогами — для полей и методов — вы уже познакомились в предыдущей главе. Правда, чтобы можно было вводить в класс компонента поля, методы и свойства средствами обозревателя классов, нужно сначала создать проект, который будет этот компонент использовать. Создайте новое приложение и присоедините к его проекту модуль CTickTape.cpp (это делается либо в контекстном меню менеджера проектов, либо выбором Project | Add to Project... в главном меню). Созданное приложение станет потом тестовой программой для проверки работоспособности нашего компонента. Пока же с ним ничего делать не нужно.
Registei-() и ValidCtrCheck()
C++Builder уже создал заготовку компонента — файл CTickTape.cpp. Он содержит на данный момент пустой конструктор и две функции — Registerf) и ValidCtrCheck():
static inline void ValidCtrCheck(CTickTape *)
{
new cricKTapenull);
}
//-----------------------
_fastcall CTickTape::CTick(TComponent*Ownet)
: TGraphicControl(Owner) { }
//----------------------------------------
namespace Cticktape {
void _fastcall PACKAGE Register()
(
TComponentClass classes[1] =
{_classid(CTickTape)};
RegisterComponents("Samples", classes, 0);
}
}
Процедура Register () вызывается при регистрации компонента в палитре C++Builder. Первый ее оператор создает массив компонентов (метаклассов), состоящий в нашем случае из единственного элемента.
С помощью ValidCtrCheck () C++Builder проверяет, можно ли создать представитель класса компонента, т. е. не является ли класс абстрактным. Если класс компонента содержит не определенные чистые виртуальные функции, компонент зарегистрирован не будет.
Ввод пользовательского кода
Завершенный код компонента показан в нижеприведенных листингах 15.1 и 15.2.
Разработка специального компонента начинается с создания файлов исходного модуля и заголовка. Это можно сделать, выбрав либо значок Component в диалоге New Items, либо пункт Component | New Component... в меню. Появится диалог, показанный на рис. 15.1.

Рис. 15.1 Диалог New Component
В этом диалоге нужно выбрать из выпадающего списка Ancestor class базовый класс компонента. Мы возьмем в качестве базового TGraphicControl. Далее нужно указать имя нового класса. (Обычно имена классов компонентов начинаются с Т, но мы назвали наш компонент CTickTape.) После этого осталось указать местоположение и имя модуля в поле Unit file name. Нажмите кнопку с многоточием, и появится стандартный диалог Save.
Все поля заполнены (убедитесь, что в поле Palette page указано Samples), и можно нажимать кнопку ОК. В редакторе кода откроется файл CTickTape. срр. Сохраните компонент (кнопкой SaveAll).
Кстати, визуальные компоненты бывают оконные (базовый класс TWinControl) и графические (базовый класс TGraphicControl). Последние, к которым относится и наш компонент, не имеют собственного окна Windows. Они располагаются в пространстве окна своего родительского объекта. Компоненты оконные, как, например, командная кнопка, имеют свое собственное окно.
Теперь, когда компонент находится в палитре, можно модифицировать программу-тестер (закомментировать код конструктора и деструктор) и разместить компонент на форме визуальным образом, а также установить нужные его свойства в инспекторе объектов. На рис. 15.5 показан размещенный на форме компонент и инспектор объектов, показывающий имеющиеся опубликованные свойства.
Мы хотим здесь продемонстрировать, что наш компонент очень прост в использовании. Форма имеет три кнопки — для запуска, остановки и выхода из программы. Также на ней размещаются три компонента TTickТаре. Для каждого из них можно задать свой размер и шрифт. Свойства TickRate были установлены в инспекторе объектов равными 10, 7 и 15. Код модуля приведен в листинге 15.4.


Рис. 15.4 Компонент конструкторе форм
Листинг 15.4. Файл ThreeU.cpp.
//-------------------------
#include<vcl.h>
#pragma hdstop
#include<ThreeU.h"
//-------------------------
#pragma packege(smart_init)
#pragma link "CTickTape"
#pragma resourse "*.dfm"
TForm1 *Form1;
//---------------------------
_fastcall TFormI: :TForml (.TComponent* Owner) :Form(Owner)
{
}
//---------------------
void _fastcalll TFormI: :Button2Click(TObject *Sender)
{
if(run)return;
CTickTapel->Started = true
CTickTape2->Started = true CTickTape3->Started = true
run = true;
while (run) (
Application->ProcessMessages() ;
if (!run) break;
CTickTapel->Tick()
CTickTape2->Tick()
CTickTape3->Tick() }
} //-----------------------------
void_fastcall TFormI::Button3Click(TObject *Sender) {
if (run) { run = false;
CTickTapel->Started = false;
CTickTape2->Started = false;
CTickTape3->Started = false;
}
}
//----------------------------------
void_fastcall TFo'rmI: :ButtonlClick (TObject *Sender) {
Button3Click(this) ;
Close () ;
} //------------------------------------
В обработчике кнопки “Старт” находится цикл ожидания:
run = true;
while (run) {
Application->ProcessMessages() ;
if (!run) break;
CTickTapel->Tick() ;
CTickTape2->Tick() ;
CTickTape3->Tick() ;
}
При нажатии этой кнопки три строки начинают бежать по экрану, каждая со своей скоростью. Как видите, все, что требуется от программиста — это достаточно часто вызывать метод Tick() строки. Время компонент будет отмерять сам. На рис. 15.5 показано запущенное приложение.

Рис. 15.5 Программа с тремя бегущими строками
Заключение
На этом наше знакомство с C++Builder заканчивается. В этой главе мы рассмотрели один из довольно сложных аспектов визуального программирования — создание визуальных компонентов. Созданный нами компонентов конечно, нельзя считать завершенным. Его нужно было бы дополнить свойствами класса TComponent, такими, как Visible, Enabled и т. д., может быть, придумать и реализовать какие-то специфические свойства и события. Скажем, можно было бы определить событие для двойного щелчка, который бы останавливал и снова запускал строку. Но все это мы оставляем на усмотрение читателя.
В этом разделе мы продемонстрируем, как в C++Builder создается визуальный компонент. Это будет компонент телетайпной ленты, отображающий “бегущую строку”. Нечто подобное вы видели в предыдущей главе.
Ключевое слово _property служит для объявления свойств и спецификации их атрибутов. Синтаксис:
property тип свойства имя = { список атрибутов свойства };
Спецификации атрибутов имеют вид атрибут [= значение] и отделяются друг от друга запятыми. Возможны следующие атрибуты:
read = поле \ get-функция
write = поле \ set-функция
stored = true | false | поле get-функция
index = целая константа
default = значение
nodefault
Для атрибутов read/write может указываться либо ассоциированное со свойством поле (тогда говорят о прямом доступе к свойству), либо имя метода доступа. Если спецификатор write опущен, то свойство можно только читать. Можно, конечно, объявить и свойство только для записи, но трудно придумать случай, когда это имело бы смысл.
Обычным является объявление прямого доступа для чтения и set-функции — для записи:
_property bool Started = {read=FStarted, write=SetStarted};
Атрибут index специфицирует целое число, которое должно передаваться в качестве единственного аргумента get-функции (обычно последняя параметров не имеет) и первого аргумента set-функции (которая обычно имеет один параметр — значение для свойства):
private:
int FCoords [4];
int _fastcall GetCoords(int Index) {
return FCoords[Index];
void _fastcall SetCoords (int Index, int value) FCoords[Index] = value;
}
public:
_property int Left = {read=GetCoords,
write=SetCoords, index=0};
_property int Top = (read=GetCoords,
write=SetCoords, index=l);
_property int Right = (read=GetCoords,
write=SetCoords, index=2} ;
_property int Bottom = {read=GetCoords,
write=SetCoords, index=3};
Атрибуты stored, default и nodefault yназываются спецификаторами хранения
Они не влияют на поведение программы и специфицируются обычно только для опубликованных свойств. В частности, они определяют. будет ли значение свойства сохраняться в файле формы. Если атрибут stored равен true и значение свойства отлично от указанного в атрибуте default, значение свойства сохраняется, в противном случае — нет. Если спецификатор stored опущен, для него принимается значение true.
В разделе класса _published объявляются опубликованные свойства. Этот раздел могут иметь только классы, производные от TObject.
В качестве спецификатора доступа _published эквивалентно public. Разница между ними в том, что опубликованные свойства доступны и в режиме проектирования через инспектор объектов, в то время как к открытым свойствам можно обращаться только программно во время выполнения.
В разделе _published нельзя объявлять конструкторы и деструкторы, а также свойства-массивы. Объявляемые в нем поля должны принадлежать к классовому типу.
Реализация визуальной среды программирования требует расширения языковых средств, поскольку невозможно на стандартном C++ осуществить, например, механизм свойств. Кроме того, разработчики C++Builder ориентировались на уже существующую библиотеку VCL, разработанную для Delphi и написанную на языке Object Pascal, и потому необходимо было ввести в язык дополнения, необходимые для моделирования чуждых C++ языковых конструкций. В этом разделе мы вкратце опишем ключевые слова C++Builder, так или иначе связанные с визуальным программированием.
Идея работы компонента проста и уже знакома вам по последнему примеру предыдущей главы. Имеется битовая матрица (TBitmap), которая служит для буферизации вывода на экран строки текста. При перерисовывании ее на канву компонента выходящие за его пределы части битовой матрицы автоматически отсекаются. При каждой перерисовке позиция битовой матрицы сдвигается влево на пиксел. Когда строка совсем уйдет с экрана, восстанавливается ее исходная позиция — за правым краем компонента.
Конструктор и деструктор
Конструктор создает операцией new битовую матрицу и устанавливает значения некоторых полей и свойств (Width — унаследованное и уже опубликованное свойство). После этого он вызывает функцию Setup ().

Обратите внимание на строку со свойством ControlStyie. Оно относится к классу Set, который моделирует встроенный тип множеств языка Pascal. Перегруженная операция “ вводит элемент в множество. Стиль компонента определяет, в частности, как будет выполняться его перерисовка. Если не установить csOpaque, весь компонент перед перерисовкой, т. е. вызовом функции Paint (), о которой речь пойдет ниже, будет очищаться и текст строки будет заметно мерцать.
Деструктор удаляет выделенную в конструкторе битовую матрицу.
Метод Setup()
Этот метод производит инициализацию битовой матрицы и вызывается при первоначальном конструировании и изменении свойств компонента — шрифта, ширины или текста.
Метод копирует свойство компонента Font в соответствующее свойство канвы битовой матрицы, определяет ширину и высоту образа текстовой строки и устанавливает по ним размеры матрицы. Устанавливается также цвет фона, и строка выводится на битовую матрицу методом ее канвы TextOut () . Образ строки готов. Справа от текста в битовой матрице имеется по крайней мере одна колонка пикселов, закрашенных фоновым цветом. Благодаря этому обеспечивается очистка пространства компонента, “появляющегося” из-под конца строки при движении ее влево.
Наконец, устанавливается начальная позиция вывода битовой матрицы в графическое пространство компонента. Проверяется свойство Component-State (это множество). Оно индицирует различные аспекты статуса компонента, в частности, участвует ли компонент в визуальном проектировании приложения или он функционирует в работающей программе. Если компонент находится в режиме проектирования, начальная позиция образа строки устанавливается равной 0, т. е. строка будет выведена начиная от его левого края и в конструкторе формы будет виден установленный в инспекторе объектов текст. В противном случае позиции присваивается значение, равное ширине компонента, что помещает строку за его правым краем.

Теперь можно тестировать и отлаживать компонент. Тестировать его как полноценный компонент, установленный в палитре, еще рано. Компоненты отлаживаются сначала в виде обычных модулей, скомпонованных с тестовым приложением. Компонент не размещается на форме с помощью конструктора форм, а создается динамически. Все его начальные свойства также устанавливаются программно, а не инспектором объектов.
Заготовка тестового приложения у вас уже есть. Нужно спроектировать его форму и написать код.
Форма тестера показана на рис. 15.2. Она содержит несколько кнопок и поле редактирования (компонент TEdit). Метка “Текст” слева от поля редактора, собственно, не нужна.

Рис. 15.2 Форма текстового приложения
Что должно тестироваться? В первую очередь, конечно, что строка движется и может быть остановлена и вновь пущена установкой значения свойства Started. Затем нужно проверить, как компонент реагирует на изменения свойств и на сообщение wm_paint, которое посылается приложению, например, при открытии его окна, ранее заслоняемого окном другой программы. Наконец, нужно проверить, генерирует ли компонент предусмотренные события.
Листинг 15.3 показывает код файлов TapeU.h и TapeU.cpp (проект тестера мы назвали Таре).
Листинг 15.3. Файлы программы-тестера для CTickTape
//-------------------------------
// TapeU.h
//
#fndef TapeUH
#define TapeUH
//--------------------------------------------
#include <Classes .hpp>
#include <Controis .hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Dialogs.hpp>
#include "CTickTape.h"
//----------------------------------
class TFormI : public TForm
{
_published:
// IDE-managed Components
TButton *Buttonl;
TLabel *Labell;
TEdit *Editl;
TButton *Buttun2;
TButton *Button3;
TButton *Button4;
TButton *Button5;
TFontDialog *FontDialogl;
TButton *Button6;
CTickTape *Tape;
void _fastcall ButtonlClick(TObject *Sender);
void _fastcall Button2Click(TObject *Sender);
void _fastcall Button3Click (TObject *Sender);

Это ключевое слово имеет отношение к VCL лишь постольку, поскольку последняя включает в себя средства для работы с нитями (threads) кода В программе может инициироваться несколько потоков управления исполняющих параллельно. Эти потоки и называются нитями.
Обычно все параллельные нити используют одни и те же глобальные
переменные. Это кстати, позволяет эффективно реализовать взаимодействие между нитями. Объявление глобальной переменной с модификатором _thread приводит к тому, что для каждой нити будет создана своя копия этой переменной.
Модификатор не может применяться к объектам, требующим инициализации во время выполнения (например, переменной — объекту класса с определенным пользователем конструктором).
Чтобы установить компонент в палитру компонентов, выберите в меню Component | Install Component... В появившемся диалоге вам нужно будет задать только имя модуля компонента. По умолчанию ваш компонент будет введен в пакет dclusr50.b.pk. Компонент окажется на странице палитры Samples. Если вы не предусматриваете для него специального значка, он будет представлен значком по умолчанию.
Значок компонента
Если вы хотите, чтобы ваш компонент, как и “настоящие”, был представлен в палитре компонентов индивидуальным значком, вы, во-первых, должны предоставить для регистрации сам значок, а также сообщить C+4-Builder сведения о его местоположении. Значок (пиктограмма) должен иметь размер 24Х24 пиксела. “Прозрачным” цветом в C++Builder считается оливковый.
Значок должен находиться в компилированном файле ресурсов с расширением .res или .dcr и иметь идентификатор ресурса, соответствующий имени класса компонента, в нашем случае CTICKTAPE.
Файл ресурсов должен быть присоединен к проекту пакета, в котором будет размещаться компонент. Если ваш компонент имеет стандартное имя, начинающееся с Т, то он может быть загружен автоматически, если файл ресурсов имеет расширение .dcr, находится в том же каталоге, что и модуль компонента и имеет то же имя, что и класс, но без Т. В нашем случае имя класса нестандартное, так что придется либо присоединить значок к проекту пакета с помощью менеджера проекта, либо вручную открыть файл CBuilder5\Lib\dclusr50.bpk и ввести в него примерно такую строку:
USERES("C:\Ppojects\Chl5\TickTape\CTickTape.dcr") ;
Как вы понимаете, все это нужно сделать до того, как вы станете устанавливать компонент.
Класс AnsiString определяется в заголовке dstring.h. Конструктор этого класса перегружен, так что строки можно инициализировать различными способами — другой строкой, строкой С (указателем на char), а также целыми типами. В последнем случае строка будет содержать текстовое представление числа.
Set — это шаблон, определенный в заголовке sysset.h:
tempiate<class T, unsigned char minEl, unsigned char maxEl>
class _declspec(delphireturn) Set;
При объявлении конкретного класса нужно задать следующие параметры: тип элементов (обычто целый, символьный или перечисление), минимальное значение, которое может содержать множество (должно быть не меньше 0), и максимальное значение, которое может входить в множество (должно быть не больше 255). Вот примеры:
Set<int, 0, 31> a5et0f32;
typedef Set<char, 'A', 'Z'> SetOfUppercase;
Перегруженный конструктор создает либо пустое множество, либо копию существующего множества того же типа.
Множества можно складывать, умножать (объединение и пересечение) и вычитать (пересечение c дополнением). Операции “ и ” соответственно вводят или удаляют элемент из множества. Эти же операции могут использоваться для извлечения и передачи множеств в поток (в виде последовательностей нулей и единиц), если перед sysset.h включается заголовок iostream или директива
#define VCL_IOSTREAM
Множества Set имеют два метода:
Sets _fastcall Clear (); Удаляет из множества все элементы.
boo1 _fastcall Contains(const T el) const; Возвращает true, если множество содержит указанный элемент.
Мы не будем приводить здесь полное описание всех методов AnsiString, а расскажем только о важнейших.
int _fastcall AnsiCompare(const AnsiStringfi rhs) const.; Этот метод производит сравнение строки с указанной строкой. Сравнение производится в соответствии с текущим локалом Windows, т. е., например, в русифицированной системе сравнение строк с русским текстом должно давать правильный результат. (Честно говоря, я этого не проверял.) Соответственно результат сравнения может отличаться от результата операций сравнения, которые производятся исходя из ASCII-значений символов.
int _fastcall AnsiPos(const AnsiStringS subStr) const; Метод возвращает позицию указанной подстроки. Первому символу строки соответствует 1. Нулевой результат означает, что подстрока не найдена.
char* _fastcall с str() const; Возвращает указатель на ограниченную нулем строку С (символьный массив). Если строке ничего не присвоено, возвращается указатель на пустую строку.
const void* fastcall data() const { return Data; } Аналогичен предыдущему, но возвращает нулевой указатель в случае неприсвоенной строки.
AnsiString& _fastcall Delete(int index, int count); Удаляет заданное число символов начиная с указанной позиции. Позиция 1 соответствует первому символу строки.
enum TStringFloatFormat
(sffGeneral, sffExponent, sffFixed,
sffMumber, sffCurrency };
static AnsiString _fastcall FloatToStrF(long double value, TStringFloatFormat format, int precision, int digits) ;
Производит преобразование числа с плавающей точкой в текстовую форму в соответствии с указанным форматом.
AnsiString& _fastcall Insert(const AnsiString& sir, int index) ; Вставляет строку в указанной позиции (1 — перед первым символом). Если индекс меньше 1, он считается единицей.
static AnsiString fastcall IntToHex(int value,int digits) ; Преобразует число в строку с представлением его в виде щестнадцате-ричных цифр. Второй параметр указывает минимальное число цифр. Аналогичен Pos, но допускает многобайтовые символы.
В VCL для представления текстовых строк используются не символьные массивы и не шаблон стандартной библиотеки, а строки Object Pascal. Кроме того, многие свойства компонентов представлены множествами этого языка. Для моделирования таких объектов C++Builder реализует классы AnsiString и Set. В этом приложении мы даем краткое описание этих классов.