教
教父
Unregistered / Unconfirmed
GUEST, unregistred user!
Don't use OnCreate and OnDestroy, use C++ constructors and destructors instead
If you need to run some code during the construction of a form, you should
place the code inside the constructor of the form. If you need todo
something
while a form is being destroyed, add a destructor to your class and place the
finalization code there. Avoid using the OnCreate and OnDestroy events that
are provided by the form.
OnCreate and OnDestroy are handy to use, because you can create them from the
Object Inspector. Despite this ease of use, you should avoid them. There are
several reasons. The most important reason is that youdo
n't know when the
OnCreate and OnDestroy events will fire.
Let's look at the OnCreate event. It is triggered from inside the VCL function
TCustomForm:oCreate. OK, so who callsdo
Create? It is called from one of two
places depending on the value of the OldCreateOrder property. If
OldCreateOrder is false (ie the good setting), then
do
Create is called from
TCustomForm.AfterConstruction. AfterConstruction executes immediately after
all of the constructors for your form have finished running (how this happens
can be attributed to compiler magic). The second function that callsdo
Create
is TCustomForm::Create, the pascal constructor for the form.
This is where things get interesting. What are the consequences of triggering
an event, such as OnCreate, from inside the constructor of a base class? Well,
the consequences are serious. Recall that the base class constructors execute
before the body of your derived constructor and more importantly, before any
derived member objects have been initialized. Take this code for example:
// header file
class TForm1 : public TForm
{
__published:
void __fastcall FormCreate(TObject *Sender);
public:
AnsiString m_foo;
__fastcall TForm1(TComponent* Owner);
}
// cpp file
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
m_foo = "hello world";
}
If OldCreateOrder is true, FormCreate will execute before the derived TForm1
constructor runs, and before the m_foo member variable has been constructred.
In this code, m_foo is default constructed. This default construction happens
just after the base class constructor is called (ie when :TForm(Owner) returns).
But FormCreate is triggered from inside of the base class constructor. When
the "hello world" assignment executes, m_foo hasn't been constructed yet. It
essentiallydo
es not exist.
Assigning values to variables that haven't been constructed is not a good
thing. So what happens to this code if OldCreateOrder is true? At best, the
assignment of "hello world" will be lost. That's what happened when I ran the
code. In a worst case scenario, the app would crash. What's really scary is
that this code switches from being malformed to being perfectly legal with a
switch of the OldCreateOrder property.
Ok, so let's summarize the most important reason why OnCreate is dangerous:
because it could execute before your constructor executes and before any
member objects have been initialized. In C++, it is generally mandated that a
base class constructor should not be able to call the member functions of a
derived class. OnCreate violates this. OnDestroydo
es too, but during
destruction.
Now, you might be thinking to yourself: "hey they danger isn't OnCreate, its
that evil OldCreateOrder property. As long as OldCreateOrder is false,
OnCreate and OnDestroy are safe." This statement is for the most part correct.
While it is true that you can control the behavior of OnCreate and OnDestroy
through OldCreateOrder, it is in fact difficult to keep control of
OldCreateOrder itself. OldCreateOrder is true by default when upgrading
projects from BCB3 (true == the bad setting). And it gets stuck on true when
using form inheritance. In BCB5, OldCreateOrder, the deadliest property of all,
is not even displayed by the object inpsector unless you specifically tell the
OI to show it. In the end, it just isn't worth it. Avoid the use of OnCreate
and OnDestroy, and you won't have to worry about OldCreateOrder rearing its
ugly head.
There is another reason to avoid OnCreate, even if you have OldCreateOrder set
properly. It is inefficient to initialize objects inside of OnCreate. In the
code above, m_foo is default constructed. When the AfterConstruction event
fires the OnCreate handler, the string "hello world" is copied into m_foo.
This occurs via the construction of a temporary AnsiString object, followed
by a called to AnsiString's assignment operator. This is somewhate
ineffiectient. If all we wanted todo
was initialize m_foo with "hello world",
the most efficient method is to use direct initialization in the constructor.
Like this:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner),
m_foo("hello world")
{
}
This code initializes m_foo using the char * conversion constructor of
AnsiString. As a result, we have replaced a default constructor call, creation
of a temporary AnsiString object, and a call to the assignment operator with a
single call to a conversion constructor. Plus, this method of construction is
the C++ way ofdo
ing things, as opposed to the Delphi way.
It is our advice that BCB users pretend that OnCreate and OnDestroydo
n't exist.
You are using a C++ product, so we feel it is wise just code things the C++ way.
这一篇文章正好回答了我在 http://www.delphibbs.com/delphibbs/dispq.asp?lid=754400
中的问题,所以我把它贴到了这里。
Use new instead of Application->CreateForm
When you need to create a form in code, use the new operator instead of
calling Application->CreateForm. If the IDE puts CreateForm in your code,
then
just leave it alone. This applies primarily to the WinMain function in
your project cpp file. But for code that you write, use new instead.
Here are some differences between CreateForm and the new operator (5 is the
best):
1- If your form is the first form being constructed with CreateForm, then
it
is automatically promoted to the job of being the mainform of the app. This
may not be what you want. What if you need to display a splash screen or a
login dialog when your app starts? If you create this dialog first with
CreateForm, then
it gets promoted to be the main form. With new, thisdo
es
not happen.
2- CreateForm always sets the global application object to be the owner of
the form. You can't override this. With new, you get to explicitly pass the
owner.
3- CreateForm has to invoke the constructor for your form class. Your
constructor is off in your c++ code somewhere. Have you ever wondered how
code written in a pascal library can find and execute your constructor? How
does it even know anything about your constructor? Itdo
esn't even know
anything about your form's type (ie TForm1).
The answer is that CreateForm invokes your constructor virtually. This is
that virtual constructor stuff that everyone is always talking about. If the
idea of a virtual constructor (which should not exist in c++) makes you queasy,
then
just use new instead.
4- Because of the virtual constructor stuff, CreateForm can only call one type
of form constructor: the constructor that takes a single TComponent * for an
owner. If you write alternative constructors, then
you can't use them with
CreateForm.
5- What happens if you call CreateForm and your formdo
es not have a
constructor that takes a single TComponent * as an owner? The answer is 'bad
stuff happens'. Try it out. Create a new application. In the main form, add an
AnsiString parameter to the existing constructor. then
add a ShowMessage or
something to the body of the constructor. Like this:
__fastcall TForm1::TForm1(TComponent* Owner, const AnsiString &foo)
: TForm(Owner)
{
ShowMessage("Foo = " + foo);
}
Make sure you change the header file too. Compile and link the app (no warnings
or errors occur). Put a breakpoint on the ShowMessage call (if you can, hint
hint). Run the app. Notice anything missing?do
es the ShowMessage line ever
execute?
No itdo
esn't. How come? How can a class be constructed without calling its
constructor? The answer lies in that virtual constructor stuff. When you
change the argument pattern for the constructor, you are no longer overriding
the virtual constructor of the base class. Since CreateForm invokes the
virtual constructor, and because your classdo
es not override that virtual
function, code execution jumps straight to the constructor of the base class,
completely bypassing the derived class (ie *your* class). The initialization
of your form and all its member variables is circumvented.
For these reasons, it is wise to use operator new for code that you write.
CreateForm is one of those pascal things that C++ programmers are wise to
avoid.
If you need to run some code during the construction of a form, you should
place the code inside the constructor of the form. If you need todo
something
while a form is being destroyed, add a destructor to your class and place the
finalization code there. Avoid using the OnCreate and OnDestroy events that
are provided by the form.
OnCreate and OnDestroy are handy to use, because you can create them from the
Object Inspector. Despite this ease of use, you should avoid them. There are
several reasons. The most important reason is that youdo
n't know when the
OnCreate and OnDestroy events will fire.
Let's look at the OnCreate event. It is triggered from inside the VCL function
TCustomForm:oCreate. OK, so who callsdo
Create? It is called from one of two
places depending on the value of the OldCreateOrder property. If
OldCreateOrder is false (ie the good setting), then
do
Create is called from
TCustomForm.AfterConstruction. AfterConstruction executes immediately after
all of the constructors for your form have finished running (how this happens
can be attributed to compiler magic). The second function that callsdo
Create
is TCustomForm::Create, the pascal constructor for the form.
This is where things get interesting. What are the consequences of triggering
an event, such as OnCreate, from inside the constructor of a base class? Well,
the consequences are serious. Recall that the base class constructors execute
before the body of your derived constructor and more importantly, before any
derived member objects have been initialized. Take this code for example:
// header file
class TForm1 : public TForm
{
__published:
void __fastcall FormCreate(TObject *Sender);
public:
AnsiString m_foo;
__fastcall TForm1(TComponent* Owner);
}
// cpp file
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
m_foo = "hello world";
}
If OldCreateOrder is true, FormCreate will execute before the derived TForm1
constructor runs, and before the m_foo member variable has been constructred.
In this code, m_foo is default constructed. This default construction happens
just after the base class constructor is called (ie when :TForm(Owner) returns).
But FormCreate is triggered from inside of the base class constructor. When
the "hello world" assignment executes, m_foo hasn't been constructed yet. It
essentiallydo
es not exist.
Assigning values to variables that haven't been constructed is not a good
thing. So what happens to this code if OldCreateOrder is true? At best, the
assignment of "hello world" will be lost. That's what happened when I ran the
code. In a worst case scenario, the app would crash. What's really scary is
that this code switches from being malformed to being perfectly legal with a
switch of the OldCreateOrder property.
Ok, so let's summarize the most important reason why OnCreate is dangerous:
because it could execute before your constructor executes and before any
member objects have been initialized. In C++, it is generally mandated that a
base class constructor should not be able to call the member functions of a
derived class. OnCreate violates this. OnDestroydo
es too, but during
destruction.
Now, you might be thinking to yourself: "hey they danger isn't OnCreate, its
that evil OldCreateOrder property. As long as OldCreateOrder is false,
OnCreate and OnDestroy are safe." This statement is for the most part correct.
While it is true that you can control the behavior of OnCreate and OnDestroy
through OldCreateOrder, it is in fact difficult to keep control of
OldCreateOrder itself. OldCreateOrder is true by default when upgrading
projects from BCB3 (true == the bad setting). And it gets stuck on true when
using form inheritance. In BCB5, OldCreateOrder, the deadliest property of all,
is not even displayed by the object inpsector unless you specifically tell the
OI to show it. In the end, it just isn't worth it. Avoid the use of OnCreate
and OnDestroy, and you won't have to worry about OldCreateOrder rearing its
ugly head.
There is another reason to avoid OnCreate, even if you have OldCreateOrder set
properly. It is inefficient to initialize objects inside of OnCreate. In the
code above, m_foo is default constructed. When the AfterConstruction event
fires the OnCreate handler, the string "hello world" is copied into m_foo.
This occurs via the construction of a temporary AnsiString object, followed
by a called to AnsiString's assignment operator. This is somewhate
ineffiectient. If all we wanted todo
was initialize m_foo with "hello world",
the most efficient method is to use direct initialization in the constructor.
Like this:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner),
m_foo("hello world")
{
}
This code initializes m_foo using the char * conversion constructor of
AnsiString. As a result, we have replaced a default constructor call, creation
of a temporary AnsiString object, and a call to the assignment operator with a
single call to a conversion constructor. Plus, this method of construction is
the C++ way ofdo
ing things, as opposed to the Delphi way.
It is our advice that BCB users pretend that OnCreate and OnDestroydo
n't exist.
You are using a C++ product, so we feel it is wise just code things the C++ way.
这一篇文章正好回答了我在 http://www.delphibbs.com/delphibbs/dispq.asp?lid=754400
中的问题,所以我把它贴到了这里。
Use new instead of Application->CreateForm
When you need to create a form in code, use the new operator instead of
calling Application->CreateForm. If the IDE puts CreateForm in your code,
then
just leave it alone. This applies primarily to the WinMain function in
your project cpp file. But for code that you write, use new instead.
Here are some differences between CreateForm and the new operator (5 is the
best):
1- If your form is the first form being constructed with CreateForm, then
it
is automatically promoted to the job of being the mainform of the app. This
may not be what you want. What if you need to display a splash screen or a
login dialog when your app starts? If you create this dialog first with
CreateForm, then
it gets promoted to be the main form. With new, thisdo
es
not happen.
2- CreateForm always sets the global application object to be the owner of
the form. You can't override this. With new, you get to explicitly pass the
owner.
3- CreateForm has to invoke the constructor for your form class. Your
constructor is off in your c++ code somewhere. Have you ever wondered how
code written in a pascal library can find and execute your constructor? How
does it even know anything about your constructor? Itdo
esn't even know
anything about your form's type (ie TForm1).
The answer is that CreateForm invokes your constructor virtually. This is
that virtual constructor stuff that everyone is always talking about. If the
idea of a virtual constructor (which should not exist in c++) makes you queasy,
then
just use new instead.
4- Because of the virtual constructor stuff, CreateForm can only call one type
of form constructor: the constructor that takes a single TComponent * for an
owner. If you write alternative constructors, then
you can't use them with
CreateForm.
5- What happens if you call CreateForm and your formdo
es not have a
constructor that takes a single TComponent * as an owner? The answer is 'bad
stuff happens'. Try it out. Create a new application. In the main form, add an
AnsiString parameter to the existing constructor. then
add a ShowMessage or
something to the body of the constructor. Like this:
__fastcall TForm1::TForm1(TComponent* Owner, const AnsiString &foo)
: TForm(Owner)
{
ShowMessage("Foo = " + foo);
}
Make sure you change the header file too. Compile and link the app (no warnings
or errors occur). Put a breakpoint on the ShowMessage call (if you can, hint
hint). Run the app. Notice anything missing?do
es the ShowMessage line ever
execute?
No itdo
esn't. How come? How can a class be constructed without calling its
constructor? The answer lies in that virtual constructor stuff. When you
change the argument pattern for the constructor, you are no longer overriding
the virtual constructor of the base class. Since CreateForm invokes the
virtual constructor, and because your classdo
es not override that virtual
function, code execution jumps straight to the constructor of the base class,
completely bypassing the derived class (ie *your* class). The initialization
of your form and all its member variables is circumvented.
For these reasons, it is wise to use operator new for code that you write.
CreateForm is one of those pascal things that C++ programmers are wise to
avoid.