Игра отражений

(КоТ: сорри, сир! Переопределив new под это дело (кстати, одно из упражнений в каком-то С++-учебнике), вполне возможно и вызвать. Только потом приходится delete лечить - он-то базируется на стандартных умолчаниях. То есть, данных объекта нет, и объекту адрес не выделен, но VMT его есть. В библиотеке или там где еще, не суть. И к этой VMT можно добраться через разную там… гм… CENSORED Плюс дельфы в откровенности доступа ко всем VMT проекта, независимо, созданы ли объекты соответствующих классов). Как правило, на этом месте фанаты С++ начинают кричать, что это де нелогично, так быть не должно… Но на самом деле нет ничего плохого в том, что конструктор использует в своей работе виртуальные принципы. Никто не утверждает, что экземпляр станет объектом С или В раньше, чем он станет объектом А. Конструкторы всего лишь выполняют свою работу, не важно в каком порядке. (КоТ: От слабости кричать. Т.к. это, безусловно, бонус дельфе перед С++, но и С++-модель определенные преимущества все-таки имеет). Более того, вызов указанной виртуальной функции совершенно бесполезен. Почему? В С++ при работе каждого из конструкторов A, B, C таблица виртуальных методов VMT будет соответствовать именно тому классу, к которому принадлежит конструктор. Т.е. вызов ЛЮБЫХ виртуальных методов в конструкторе С++ теряет всякий смысл, т.к. не является виртуальным (будет вызван соответствующий метод для класса А или В, а не для С). То же касается и деструкторов С++. В ОР при работе любого из конструкторов предков VMT всегда соответствует РЕАЛЬНОМУ создаваемому классу, т.е. классу С. Вызовы виртуальных методов будут правильными. В принципе, это может создать опасную ситуацию, когда в данном виртуальном методе какой-то из наследников подразумевает, что класс уже полностью сконструирован. Именно для разрешения этой проблемы и существует виртуальный метод AfterConstruction. Теперь мы четко видим, что конструкторы ОР обладают НАМНОГО большей гибкостью и мощью. Конечно, при условии, что программист понимает, что делает. (КоТ: при условии, что программист понимает, что делает, и С++ не так уж плох ;-) А это не так уж и сложно. По крайней мере, практика показывает, что эти конструкторы не доставляют никаких хлопот программистам. А значит, увеличение мощи не уменьшило "безопасности программирования" :) Продолжим.

class E: public A,B {
  C* c_;
  D* d_;
  public:
  E(); // реализацию см.ниже
  ~E() { delete d_; delete c_; }

  }
  E::E() // конструктор класса E
  try
  : A(1), B(1), c_( new C ), d_( new D ) // список инициализации
  { //начало тела конструктора
  cout<<"Constructor body";
  }
// конец тела конструктора
  catch(...){ // ловим любое исключение
  A::~A();
  B::~B();
  delete c_;
  delete d_;
  }

Непривычное написание, не так ли? Да, в Делфях нельзя ВЕСЬ процесс конструирования поместить в блок try except.
Неправильно! Скорее можно сказать, что в ОР нельзя НЕ поместить весь процесс конструирования в блок try…except. При вызове конструктора ОР как классового (статического, в терминах С++) метода (т.е. через классовую ссылку) блок try…except устанавливается АВТОМАТИЧЕСКИ. При возникновении любого необработанного исключения в конструкторе автоматически вызывается деструктор. Это, однако, не мешает вписать в тело конструктора свои блоки обработки исключений, в том числе и для полной, безопасной обработки некоторых их типов.
Здесь уместно более подробно осветить различия в способах вызова конструкторов ОР. Как я уже говорил, конструктору компилятором неявно передается параметр, который говорит, что он вызывается как классовый или как обычный метод.
В случае классового метода:
устанавливается блок try…except;
вызывается виртуальный метод NewInstance, выделяющий память под экземпляр класса. В случае переопределения Вами этого метода:
размер экземпляра можно получить методом InstanceSize;
память нужно очистить методом InitInstance;
отрабатывает тело конструктора;
вызывается виртуальный метод AfterConstruction.
В случае обычного метода выполняется только тело конструктора. Блок try…except НЕ устанавливается. Так вызываются все собственные конструкторы и конструкторы предков из тела какого-либо конструктора класса (они все равно попадут в установленный блок обработки исключений). Конструктор может быть вызван где угодно. Главное - использовать объектную ссылку (Self.Create), а не классовую. Условно, реальный код конструктора мог бы выглядеть так:

function TSomething.Create(IsClassRef: boolean): TSomething;

begin

 if IsClassRef then

 try

  Self := TSomthing.NewInstance;

  InitInstance(Self);

  Self.Create(False); // Тело конструктора,

  // написанное разработчиком

  Self.AfterConstruction;

 except

  Self.Destroy; // Если что - харакири :)

 end

 else

  Self.Create(False); // Тело конструктора

 Result := Self;

end;

Аналогичная песня с деструкторами. Но здесь обойдемся без лишних объяснений:

procedure TSomething.Destroy(Deallocate: boolean);

begin

 if Deallocate then

  Self.BeforeDestruction;

 Self.Destroy(False);

 if Deallocate then

 begin

  Self.CleanupInstance;

  Self.FreeInstance;

 end;

end;

Еще раз замечу, что это чисто гипотетический код, создаваемый компилятором, а не реализация конкретного класса. Конечно, в нем нет никаких рекурсивных вызовов. Продолжим.
Но как же быть с динамическими ресурсами? Спросите вы. Все очень просто:

 E::E() // конструктор класса E

  try

  : A(1), B(1), c_( NULL ), d_( NULL ) // список инициализации

  { //начало тела конструктора

  try{

  c_ = new C;

  d_ = new D;

  cout<<"Constructor body";

  }


  catch(...){

  if(c_) delete C;

  if(d_) delete D;

  throw;

  }


  } // конец тела конструктора

  catch(...){ // ловим любое исключение

  throw E_ErrorCreate();

  }

Видно, что я использовал блок try...catch только для "перевода" одного исключения в другое. И назначение этого блока только такое и никакого другого. Использование его в других целях может привести к гадостям (КоТ: если ножом кухонным неправильно пользоваться, это МОЖЕТ привести даже к смерти… Но ведь не обязательно же приводит! Так претензии к ножу (языку), или к кривым рукам?) ,поэтому в некоторых С++ компиляторах (фирмы Борланд например) эта возможность от греха подальше убрана. Вы еще не заскучали?
Нет, Дмитрий, с Вами не соскучишься :)
Здесь хочу лишь заметить, что в ОР нет необходимости чистить ресурсы в конструкторе. На это есть деструктор! (КоТ: Вот!!!) Логично, не так ли? Зачем плодить двойной код. А вот конструкции вида
if Assigned(MyObject1) then
FreeAndNil(MyObject1);
if Assigned(MyObject2) then
FreeAndNil(MyObject2);
ОЧЕНЬ рекомендуется использовать именно в деструкторе. Это хороший стиль. (КоТ: что да, то да.) Конструкция аналогичная if (c_) delete c_ (кстати, здесь была ошибка).
(КоТ: с != 0 бывает, т.к NULL-тип машинно-зависимый. Но пустой указатель где-то представлен, напр, отрицательным числом. Если мне понадобилось, я бы писал
if (С ! = NULL) // что надо сделать с С
хотя Страуструп и советует использовать 0 вместо NULL - в третьей редакции книги. В первой, помнится, советовал обратное ;)
Ведь деструктор может быть вызван в любой момент работы конструктора, и часть ресурсов будет неинициализирована.
Привел я этот пример не для демонстрации возможностей блока try...catch, а для того чтобы показать как С++ сам делает безопасным процесс "конструирования" класса. В Делфи все это ложиться на хрупкие плЭчи программера.
(КоТ: "Врать не надо по телефону" (с) Булгаков)
Теперь мы видим, кто действительно "сам делает безопасным процесс конструирования класса", а кто перекладывает все это на чьи-то "хрупкие плЭчи".
Кстати о Делфях, я там не нашел аналог функции С++ - uncaught_exception() - показывает статус стека исключений. Благодаря этой функции ваш деструктор знает - нормальное это "устранение" класса или не нормальное. По-моему, очень даже пользительно.
Что значит ненормальное устранение класса? Может, мы еще будем считать возникновение исключения ненормальной ситуацией? Между прочим, на исключениях вполне можно выстроить логику работы класса или библиотеки. В ОР этому, кстати, очень способствуют такие преимущества модели исключений перед ANSI C++, как наличие общего предка исключений (и то, что это вообще классы, а не абы что) и наличие блока try…finally (ну это просто добавляет удобств по сравнению с try…catch(…){ throw; })
Поэтому не совсем понятно, зачем понадобилась некая функция uncaught_exception(). Зачем лезть в идеологию работы исключений со своим уставом? Ведь они как раз и избавляют разработчика от чрезмерного применения if. Это еще называется реактивной моделью программирования. Но, раз есть спрос, то есть и предложение:
ExceptAddr function - returns the address at which the current exception was raised.
ExceptObject function - returns a reference to the object associated with the current exception.
ExceptProc variable - points to the lowest-level RTL exception handler.
Фича в том, что оператор new уже выделил память для экземпляра класса TObject, а тут ррраз! И исключение! Что делает С++? Он тут же освободит память - не надо ставить блок try...catch. Все сделает С++. Ну, как говориться, приятная неожиданность.
(КоТ: это стандарт, и кто не знает его, как может говорить, что знает С++?)
Ну, это только для тех, кто не очень хорошо знает ОР и С++. Впрочем, такие "детские" неожиданности не избавляют программера в С++ от необходимости защиты динамических ресурсов. В ОР же в это время можно попить пива ;)
Я не привожу примеры реализации более полезных УМНЫХ указателей, реализующих сборку мусора и правильную работу с ресурсами вообще.
Судя по всему, автор прочитал книгу Джефа Элджера "C++", испестренную идеями УМНЫХ, ВЕДУЩИХ, ГЕНИАЛЬНЫХ указателей и сборки мусора. Здесь хочу заметить, что я иногда читаю книги с карандашом в руке. Это очень хорошая, умная (КоТ: ведущая и гениальная ;-) книга про С++. Только во время ее чтения, постоянно задумываешься, а как можно сделать тоже самое в ОР. В результате после прочтения книга превратилась в записную книжку, испестренную замечаниями о том, насколько проще и красивее выглядела бы в ОР большая часть предлагаемых решений. Для интересующихся - основная идея в использовании свойств и интерфейсов.

Отправить комментарий

Проверка
Антиспам проверка
Image CAPTCHA
...