Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Как объекту отказаться себя создавать?


Автор: Ibragim 4.10.2004, 01:00
Всем ночи доброй.
Такая ситуация: объект класса TMyClass сам решает при создании, есть ли подходящие условия для создания себя. Нужно, чтобы пользователь вызывал Create, а получал в ответ nil - и при этом не засорялась память и т.п. То есть хочется сделать так:

constructor TMyClass.Create
begin
<...>
if <нехочу создаваться> then
self := nil;
<...>
end

Как это правильно сделать, чтобы объект не создавался? Чтобы не выделялась память под переменные, и т.д.
Заранее спасибо.

Автор: ДЫМ 4.10.2004, 01:32
Попробуй сгенерировать "тихое исключение"
if <нехочу создаваться> then Abort;

Автор: <Spawn> 4.10.2004, 05:05
Ibragim Примерно так:

Код
EMyException=class(Exception);

...

constructor TMyClass.Create(...);
begin
 ....
 if <какое то условие> then
   raise EMyException.Create("Исключение из конструктора");
end;


try
 MyClass = TMyClass.Create(...);
except
 on E: EMyException do
   ShowMessage(E.Message);
end;



Автор: Ibragim 4.10.2004, 05:31
Понятно... Будем пробовать. А так чтобы

MyObject : TMyClass;
begin
MyObject := TMyClass.Create;
if MyObjec = nil then ....


никак сделать нельзя?

Моим классом будут пользоваться другие, и невозможность создать объект класса у меня не исключение, а довольно стандартная ситуация. Предполагается, что пользователь моего класса обязательно проверит - создалось что-то или нет. И если просто возвращать nil вместо указателя на класс было бы самое оно. Но если нет - что ж, будем делать как советовали.

В любом случае большое спасибо.

Автор: p0s0l 4.10.2004, 12:01
Можно сделать так:
Код
type
 TMyClass = class (TObject)
 protected
   AbortConstruction : boolean;
 public
   procedure AfterConstruction; override;
   constructor Create (Condition : boolean);
 end;

constructor TMyClass.Create;
begin
 inherited Create;
// ...
 if Condition then AbortConstruction := True;
// ...
end;

procedure TMyClass.AfterConstruction;
begin
 if AbortConstruction then
 begin
   Free;
   asm xor esi, esi end;
 end;
end;
В конце конструктора насильно и всегда вызывается AfterConstruction, поэтому если прямо в конструкторе сделать Free или Self := nil, то получится Access Violation. Поэтому Free делаем в AfterConstruction, а также обнуляем регистр ESI - в нём хранится Self (эквивалент Self := nil, если бы мы его делали не в AfterConstruction, а в Create)...
Пример:
Код
var
 c : TMyClass;
begin
 c := TMyClass.Create(false); // c <> nil
 c := TMyClass.Create(true); // c = nil
end;

Цитата
Как это правильно сделать, чтобы объект не создавался?
Предложенный способ не совсем правильный sad.gif

Автор: Ibragim 4.10.2004, 14:02
Спасибо. А что значит "предложенный способ не совсем правильный"? Какой способ? Твой? Тогда как совсем правильно?

Автор: p0s0l 4.10.2004, 15:17
Ну да... То, что я предложил - не совсем правильно, потому что делается xor esi, esi... Это типа обмана компилятора.

Щас в голову пришёл более правильный способ (вернее, в нём уже ничего неправильного не будет):
Код

type
 TMyClass = class
 protected
   AbortConstruction : boolean;
   constructor HiddenCreate (Condition : boolean);
 public
   class function Create (Condition : boolean) : TMyClass;
 end;

class function TMyClass.Create;
begin
 Result := TMyClass.HiddenCreate(Condition);
 if Result.AbortConstruction then
 begin
   Result.Free;
   Result := nil;
 end;
end;

constructor TMyClass.HiddenCreate;
begin
 inherited Create;
// ...
 if not Condition then
 begin
   AbortConstruction := true;
   Exit;
 end;
// ...
end;

// пример:
procedure TForm1.Button3Click(Sender: TObject);
var c : TMyClass;
begin
 c := TMyClass.Create(false); // c = nil
 c := TMyClass.Create(true); // c <> nil
end;


Автор: <Spawn> 4.10.2004, 16:54
Кстати, интересный вопрос, вот такой код показывает что указатель при моем примере не инициализируется:

Код
try
   t := TTest.Create;//В TTest.Create вызываем injerited Create; raise Exception.Create('');
 except
   ShowMessage(IntToStr(DWORD(t)));
 end;


Но вот вопрос, а все ли данные от TObject корректно высвобождаются? Ни где про это не встречал описание, а всегда вызывал исключение в случае надобности, но правильно ли этоsmile.gif Только сейчас меня эти мысли посетилиsmile.gif

p0s0l Ну тут даже и class function не нужно делатьsmile.gif Это статический метод -> он все равно всегда при жизни приложения будет в памяти. Хотя для выделения принадлежности к какому то классу может это и есть хорошо. Но вот что мне в этом подходе не нравиться - а если в конструкторе будет выделено пару десятков\сотен метров памяти(например, для работы с изображениями\видео\получение больших наборов данных от СУБД и т.д), то это будет не совсем эффективно.

Автор: p0s0l 4.10.2004, 18:01
Цитата
Ну тут даже и class function не нужно делать Это статический метод -> он все равно всегда при жизни приложения будет в памяти.
А разве обычные методы класса не всегда в памяти ? Динамически подгружаются/выгружаются что ли ? smile.gif
Если я в программе использую TBitmap, то в .exe (и в памяти) тоже будет код конструктора TBitmap.Create.
Или я неправильно тебя понял... Объясни подробнее...

Цитата
Хотя для выделения принадлежности к какому то классу может это и есть хорошо.
Эта и есть причина. Конечно, можно было сделать отдельную функцию, типа такого:
Код
function CreateMyClass : TMyClass;
begin
 Result := TMyClass.Create;
end;
Но это как-то некрасиво. Уже все привыкли делать
c := TMyClass.Create
а не
c := CreateMyClass;

Тем более, что если применить приведённый подход, то никакой разницы ни для программиста, ни для юзера не будет. Ты разве почуствуешь разницу, Create - это конструктор или функция ? smile.gif Я лично не почувствую. Если принесут мне модуль, где описан класс TMyClass, то я на автомате напишу
c := TMyClass.Create(..), и даже не задумаюсь, конструктор это или функция...
И при таком подходе возможно то, чего хочет Ibragim:
MyObject := TMyClass.Create;
if MyObject = nil then ....

Цитата
Но вот что мне в этом подходе не нравиться - а если в конструкторе будет выделено пару десятков\сотен метров памяти(например, для работы с изображениями\видео\получение больших наборов данных от СУБД и т.д), то это будет не совсем эффективно.
Это уже не от подхода зависит, а от программиста, и от подхода не зависит...

Сравни 2 варианта:
Код
constructor TMyClass.Create;
begin
...
 <выделение 1 ГБ памяти>
...
 if <какое то условие> then
 begin
    < освобождение 1 ГБ памяти >;
   raise EMyException.Create("Исключение из конструктора");
 end
...
end;

и второй вариант:
Код
constructor TMyClass.HiddenCreate;
begin
...
 <выделение 1 ГБ памяти>
...
if <какое то условие> then
 begin    
    < освобождение 1 ГБ памяти >;
    AbortConstruction := true;
    Exit;
 end;
...
end;
В чём разница ? Не вижу никаких отличий... Отличие моего от твоего метода в том, что в первом случае генерится исключение, во втором случае - делается Exit.
Чтобы окончательно развеять твои сомнения, можно заменить все эти манипуляции с AbortConstruction и Exit теми же исключениями, и ловить эти исключения в класс-функции Create...
Тогда это будет выглядеть так:
Код
type
 TMyClass = class
 protected
   constructor HiddenCreate (Condition : boolean);
 public
   class function Create (Condition : boolean) : TMyClass;
 end;

class function TMyClass.Create;
begin
 try
   Result := TMyClass.HiddenCreate(Condition);
 except
   Result := nil;
 end;
end;

constructor TMyClass.HiddenCreate;
begin
 inherited Create;
// ...
 if not Condition then
   raise Exception.Create ('Error');
// ...
end;


Цитата
Но вот вопрос, а все ли данные от TObject корректно высвобождаются? Ни где про это не встречал описание, а всегда вызывал исключение в случае надобности, но правильно ли это Только сейчас меня эти мысли посетили
Если в конструкторе произойдёт исключение, то объект автоматически уничтожится (вызовется Destroy)...


Автор: Ibragim 4.10.2004, 19:11
Спасибо за интересные рассказы. В моем случае тормозов не будет - объемы выделения памяти не те. Хотя в контексте интересно.
Тогда Гуру - хотите еще вопрос? Совсем сложный?
Я даже и не спрашивал - но вдруг и так можно...

Значит есть большое дерево классов, каждый класс, понятное дело, знает о своем родителе но ничего не ведает о потомке. Нужно обойти это дерево классов и создать наиболее подходящий объект по таким критериям:

1. единственный параметр - строка, получаемая в процессе работы (положим, из физического порта)
2. каждый класс может оценить по этой строке возможность создания экземпляра себя, и только саму возможность: могу создаться/не могу создаться
3. наиболее подходящий объект - это объект на наиболее удаленной от корня дерева ветке.
4. два объекта считаем одинаково подходящими, если они на одинаковом удалении от корня дерева - тогда одинаково хорошо любое решение.

Для пользователя все должно выглядеть так:
MyObject: TBaseClass;
MyObject := TBaseClass.Create;

а в MyObject - указатель на наиболее подходящий объект (ну или nil).
прошу заметить из приведенного примера, что менеджера классов создавать нельзя - классы сами должны между собой "договориться" - какой объект создавать.

Автор: p0s0l 4.10.2004, 22:35
Проще было бы сделать так:
Код
function SmartCreate (s : string) : TBaseClass;
begin
 if (s = 'abc') then Result := TClass1.Create
 else if (s = 'ceffvsdf') then Result := TClass2.Create
// и т.д.
end;


Если же идти таким способом, как ты сказал, то всё равно надо делать что-то для того, чтобы знать, какие возможные классы можно создать... Имхо, легче всего это сделать, если объявить массив, в котором будут указаны всевозможные наследники.

Пример. Есть такое дерево:
Код

           /----> TClass1 ----> TChild1OfClass1
TBaseClass +
           \----> TClass2 ----> TChild1OfClass2
                          \---> TChild2OfClass2

1 и 2 критерии будут проверяться функцией CheckString. Эта функция переписывается в каждом новом наследнике от TBaseClass и от его потомков.
3 и 4 критерии будут осуществляться в TBaseClass.

Т.к. constructor TBaseClass сам по себе создаёт экземпляр TBaseClass (а нам надо создать наследника от него), то вместо конструктора надо использовать функцию/класс-функцию.
И опять же, т.к. для 2 критерия надо проверять для каждого класса истинность условия, то придётся эту функцию проверки делать класс-функцией. Обычным методом сделать нельзя, т.к. самого экземпляра класса не существует еще (буди что временно создавать, но это криво).
Код
type
 TBaseClass = class
 protected
   constructor HiddenCreate;
 public
   class function CheckString (const s : string) : boolean; virtual; abstract;
   class function Create(const s : string) : TBaseClass;
 end;

 TClass1 = class(TBaseClass)
   class function CheckString (const s : string) : boolean; override;
 end;

// и т.д.

class function TClass1.CheckString;
begin
 Result := ( s = 'aaa' );
end;

class function TChild1OfClass1.CheckString;
begin
 Result := ( s = 'aaa' );
end;

// и т.д. (в других классах-наследниках, понятно, другие условия)

// Массив классовых указателей:
type TChild = class of TBaseClass;
const Childs : array [0..4] of TChild =
     (TClass1, TClass2, TChild1OfClass1, TChild2OfClass1, TChild1OfClass2);

// TBaseClass
constructor TBaseClass.HiddenCreate;
begin
 inherited Create;
end;

class function TBaseClass.Create;
var
 i, lvl, maxlvl : integer;
 best : TChild;

begin
 maxlvl := 0;
 best := nil;
 for i := 0 to High(Childs) do
   if Childs[i].CheckString(s) then
     if CountGenerations(TBaseClass, Childs[i]) > maxlvl then
     begin
       maxlvl := CountGenerations(TBaseClass, Childs[i]);
       best := Childs[i];
     end;
 if best <> nil then Result := best.HiddenCreate
   else Result := nil;
end;


Теперь если сделать так:
Код
var
 MyObject : TBaseClass;
begin
 MyObject := TBaseClass.Create('aaa');
 Caption := MyObject.ClassName;
end;
То MyObject будет TChild1OfClass1...

Есть способ без всяких массивов и других специальных прибамбасов. Только он опять же будет не совсем правильный...
И к тому же, в любом случае, кроме объявления и реализации класса, надо делать ссылку на него (например, в глобальной переменной), иначе этот класс не будет участвовать в компиляции...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)