компании Borland, и можно надеяться, что в следующей версии Borland C++ Builder они будут исправлены, а пока... пока читайте "Мир ПК". Публикуем фрагменты оригиналов программ с ошибками и предлагаем способы предотвращения ошибочных ситуаций.
Первый листинг (см. листинг 1) демонстрирует ошибку, возникающую при использовании свойств в inline-методах. Если отключить функцию inline, то программа работает превосходно.
Ошибка скрывается в строчке:
v.dblValue=v.dblValue+3;
заставляющей происходить странные вещи. К примеру, вызывается конструктор формы, хотя в нем нет необходимости, - это, собственно говоря, и приводит к сбою. Однако не только это. В приведенном фрагменте нарушено одно правило: все операции со свойствами должны проводиться отдельно. В этом случае лучше всего будет воспользоваться промежуточной переменной:
double tempVar = v.dblValue;
v.dblValue = tempVar+3;
Теперь все формальности соблюдены, и C++ Builder сгенерирует превосходно работающий код.
Очередная ошибка также скрывается в inline-методах (см. листинг 2). Взгляните на следующую строку:
int Get(int aI) const { return (int) List->Items[aI]; }
Казалось бы, все в порядке. Переданный через параметр индекс подставляется в список, и полученный из него объект приводится к типу int. Однако этот параметр теряется, а вместо индекса подставляется самое первое число из списка List->Items. В данном примере единственный сохраненный в списке объект - число 45. Именно оно и подставляется вместо индекса. Это значит, что если список имеет менее 45 элементов, возникнет исключительная ситуация выхода за предел диапазона. Однако если список достаточно велик, ошибка не возникнет, а будет возвращено случайное значение. Ошибка возникает из-за того, что C++ Builder в inline-методах вычисляет значение в неверном порядке. По правилам языка Cи++ сначала должна быть произведена операция [], затем -> и уже потом производится приведение к типу. Однако, как показано в листинге 2, сначала производится приведение к int указателя List, а уже потом все остальное. Исправить ситуацию можно двумя способами. Первый - применить новый способ приведения:
int Get(int aI) const { return int( List->Items[aI] ); }
Тогда компилятор, вызывая конструктор объекта класса int, вынужден сначала вычислить выражение в скобках. И второй: можно просто заставить компилятор сначала вычислить приложение, а уже затем сделать приведение. Для этого достаточно добавить скобки:
int Get(int aI) const { return ( int ) ( List->Items[aI] ); }
Третья ошибка C++ Builder проявляется в момент генерации исключительной ситуации. Пример, приведенный в листинге 3, показывает ошибочную работу компилятора по обработке перехваченного исключения, генерируемого пользователем:
throw 1;
Метод, в котором происходит исключение, описан так, что возвращает экземпляр объекта класса KKK:
KKK TEv::eval()
На деле еще до создания экземпляра этого класса компилятором все равно вызывается для него деструктор. Это серьезная ошибка. К сожалению, проблема эта легко не решается. Зато здесь три решения "в лоб". Самое простое - принять во внимание документацию по Visual Component Library и создавать все объекты этой библиотеки только динамически. Это значит, что вместо строки, описанной в классе TEv:
String c;
нужно написать:
String c = new AnsiString("");
Другое решение предлагает создать пустой конструктор в классе TEv:
TEv() {};
И наконец, самое прямолинейное решение - дайте же компилятору то, что он требует, т. е. деструктор.
Только пусть это будет пустой деструктор класса KKK:
~KKK() {};
Трудно сказать, чем вызвана эта ошибка, скорее всего, неправильной инициализацией стека в момент установки обработчиков исключительной ситуации.
//
#pragma resource "*.dfm"
TForm1 *Form1;
class DV
{
double value;
public:
double GetDblValue(){return value;}
double SetDblValue(double aVal){return value=aVal;}
__property double dblValue= {read=GetDblValue,write=SetDblValue};
};
//
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//
void __fastcall TForm1::Button1Click(TObject *Sender)
{
DV v;
v.dblValue=2;
// При расширении inline-функций в следующей строке происходит
// завершение программы с сообщением об ошибке
// 'Abnormal program termination' или 'access violation...' и указывается адрес.
// Если расширение inline-функций отключено, все в порядке.
v.dblValue=v.dblValue+3;
ShowMessage(v.dblValue);
}
Листинг 2
struct TStruct
{
TList * const List;
TStruct() : List(new TList) {}
~TStruct(){ delete List; }
int Get(int aI) const { return (int) List->Items[aI]; }
};
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TStruct s;
s.List->Add( (void*) 45);
//Несмотря на то что мы здесь передаем индекс 0, в List->Items
//попадает совершенно произвольное значение
const int val=s.Get(0);
ShowMessage(val);
}
//
Листинг 3
//
...
class KKK
{
//Необходима хотя бы одна переменная типа AnsiString
String sc;
public:
KKK(const KKK &aV){}
KKK(){}
};
class TEv
{
String c;
public:
KKK eval();
};
KKK TEv::eval()
{
throw 1;
// До этого места выполнение не дойдет
//return KKK();
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TEv e;
try{
//В этом вызове должен произойти throw на catch(int),
//но программа завершается с сообщением об ошибке 'Abnormal program termination'
e.eval();
}
catch(int){
ShowMessage("Поймали целое");
}
catch(...){
ShowMessage("Поймали что-то еще");}
ShowMessage("Пошли дальше...");
}
//