📄 suggest.htm
字号:
<P>
Through experimentation, I have found that the += operator does work on integer properties, such as the <TT>Width</TT>
property of the form. However, in order to avoid confusion, it may be wise to avoid the += operator on all properties.
The same can be said for the other combination operators <TT>(-= , *= , /=, ^= ,</TT> and so on).
</P>
<BR>
<H3>
<A NAME="tlist">Don't forget to delete pointers in a TList</A>
</H3>
<P>
If you use <TT>TList</TT> to store pointers, make sure that you delete the pointers. <TT>TList</TT> makes
no assumptions about the data it holds, and it does not delete the pointers in the list when it is deleted.
That is your responsibility.
</P>
<BR>
<H3>
<A NAME="voidptr">Don't delete the void pointers in a TList</A>
</H3>
<P>
Yes, it is your responsibility to delete pointers that are contained in a <TT>TList</TT>, but you have to be careful
about how you do it. Consider the following code.
</P>
<pre>
<font color="navy">// construct a list of bitmaps</font>
TList <b>*</b>list <b>=</b> <b>new</b> TList<b>;</b>
<b>const</b> <b>int</b> nButtonCount <b>=</b> <font color="blue">100</font><b>;</b>
<b>for</b><b>(</b><b>int</b> j<b>=</b><font color="blue">0</font><b>;</b> j<nButtonCount<b>;</b> j<b>++</b><b>)</b>
<b>{</b>
list<b>-></b>Add<b>(</b><b>new</b> Graphics<b>:</b><b>:</b>TBitmap<b>)</b><b>;</b>
<b>}</b>
<font color="navy">// do something with the list</font>
<font color="navy">// now delete the list</font>
<b>for</b><b>(</b><b>int</b> j<b>=</b><font color="blue">0</font><b>;</b> j< list<b>-></b>Count<b>;</b> j<b>++</b><b>)</b>
<b>{</b>
<b>delete</b> list<b>-></b>Items<b>[</b>j<b>]</b><b>;</b>
<b>}</b>
<b>delete</b> list<b>;</b>
</pre>
<P>
Everything looks good on the surface. You construct a list and add some items. When you are done with the list, you
loop through and delete the pointers in the list before deleting the list itself. It all looks good, but it doesn't
work correctly. In fact, the code usually crashes. The problem is that the <TT>Items</TT> property of <TT>TList</TT> returns a <TT>void</TT> pointer.
So how do you destroy a <TT>void</TT> pointer? I don't know, but I do know that deleting a <TT>void</TT> pointer is
not the same as deleting a <TT>TBitmap</TT> pointer.
</P>
<P>
Since <TT>TList</TT> doesn't know what kind of stuff you are storing in it, it returns a <TT>void</TT> pointer when you
fetch an item from the <TT>Items</TT> array. When you try to delete a <TT>void</TT> pointer, the operator
<TT>delete</TT> doesn't really know what you are asking it to do. It doesn't know that the pointer is really a
<TT>TBitmap</TT> pointer. As a result, the destructor for <TT>TBitmap</TT> will not be called when you delete the
items in the for loop. In order to correctly destroy the items in the list, you must tell the compiler
what type of objects you are deleting. The correct code looks like this:
</P>
<pre>
<font color="navy">// construct a list of bitmaps</font>
TList <b>*</b>list <b>=</b> <b>new</b> TList<b>;</b>
<b>const</b> <b>int</b> nButtonCount <b>=</b> <font color="blue">100</font><b>;</b>
<b>for</b><b>(</b><b>int</b> j<b>=</b><font color="blue">0</font><b>;</b> j<nButtonCount<b>;</b> j<b>++</b><b>)</b>
<b>{</b>
list<b>-></b>Add<b>(</b><b>new</b> Graphics<b>:</b><b>:</b>TBitmap<b>)</b><b>;</b>
<b>}</b>
<font color="navy">// do something with the list</font>
<font color="navy">// now delete the list</font>
<b>for</b><b>(</b><b>int</b> j<b>=</b><font color="blue">0</font><b>;</b> j< list<b>-></b>Count<b>;</b> j<b>++</b><b>)</b>
<b>{</b>
<b>delete</b> <b>reinterpret_cast</b><Graphics<b>:</b><b>:</b>TBitmap <b>*</b><b>></b><b>(</b>list<b>-></b>Items<b>[</b>j<b>]</b><b>)</b><b>;</b>
<b>}</b>
<b>delete</b> list<b>;</b>
</pre>
<P>
There are a couple of additional things to think about. First, remember that <TT>reinterpret_cast</TT> is not a type
safe cast. It does not check that the items are the correct type. Secondly, don't bother trying to use
<TT>dynamic_cast</TT> because you cannot use <TT>dynamic_cast</TT> on a <TT>void pointer</TT>. Thirdly, if your list
contains different types of objects, cast the objects to the most immediate, common base class. For example, if your list contains
<TT>TEdit</TT> pointers and <TT>TButton</TT> pointers, then cast the items to a <TT>TWinControl</TT> pointer before
deleting. In order for this to work, the destructors in your classes must be declared as virtual. Also, this won't work
if the pointers do not have a common base class. Finally, many of these headaches can be avoided by using a type-safe
derivative of <TT>TList</TT> or one of the typesafe template containers in the STL.
</P>
<BR>
<H3>
<A NAME="overuse_tlist">Don't overuse the TList class</A>
</H3>
<P>
C++ programmers should generally use the containers from the STL instead of using
<TT>TList</TT>. There are several reasons. First of all, STL code is portable between
compilers and operating systems. Secondly, <TT>TList</TT> is not typesafe. When you
use <TT>TList</TT> as a container for <TT>TButton</TT> objects, nothing prevents you
from adding a <TT>TListBox</TT> to your container. STL containers prevent you from adding
the wrong types of objects to the container. Lastly, <TT>TList</TT> is very poor at storing
large numbers of objects (> 5000 or so). The STL containers are much more efficient at storing
large numbers of objects.
</P>
<BR>
<H3>
<A NAME="parent">Don't forget to set the Parent property of a control created at runtime</A>
</H3>
<P>
When you create a control at runtime, the control will not appear if you forget to set the control's
<TT>Parent</TT> property. You will usually set the <TT>Parent</TT> to be a form, a panel, or a group box.
See the FAQ on how to <A TARGET=_top HREF="../vcl Controls and Classes/faq2.htm">create a control at runtime</A> for more info.
</P>
<BR>
<H3>
<A NAME="mdiparent">Don't set the Parent property of MDI child forms</A>
</H3>
<P>
Doing so will cause runtime problems in your program. To create an MDI child form, simply construct the form object.
Don't worry about the <TT>Parent</TT> property.
</P>
<BR>
<H3>
<A NAME="oncreate">Don't use OnCreate and OnDestroy, use C++ constructors and destructors instead</A>
</H3>
<P>
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 to do something while a form is being destroyed, add a destructor to your
class and place the finalization code there. Avoid using the <TT>OnCreate</TT> and <TT>OnDestroy</TT> events that are provided
by the form.
</P>
<P>
<TT>OnCreate</TT> and <TT>OnDestroy</TT> 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 you don't know when the <TT>OnCreate</TT>
and <TT>OnDestroy</TT> events will fire.
</p>
<P>
Let's look at the <TT>OnCreate</TT> event. It is triggered from inside the VCL function <TT>TCustomForm::DoCreate</TT>. OK, so who calls
<TT>DoCreate</TT>? It is called from one of two places depending on the value of the <TT>OldCreateOrder</TT> property. If <TT>OldCreateOrder</TT> is
false (ie the good setting), then <TT>DoCreate</TT> is called from <TT>TCustomForm.AfterConstruction</TT>. <TT>AfterConstruction</TT> 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 calls <TT>DoCreate</TT> is <TT>TCustomForm::Create</TT>, the pascal constructor for the form.
</P>
<P>
This is where things get interesting. What are the consequences of triggering an event, such as <TT>OnCreate</TT>, 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:
<pre>
<font color="navy">// header file</font>
<b>class</b> TForm1 <b>:</b> <b>public</b> TForm
<b>{</b>
<b>__published</b><b>:</b>
<b>void</b> <b>__fastcall</b> FormCreate<b>(</b>TObject <b>*</b>Sender<b>)</b><b>;</b>
<b>public</b><b>:</b>
AnsiString m_foo<b>;</b>
<b>__fastcall</b> TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b><b>;</b>
<b>}</b>
<font color="navy">// cpp file</font>
<b>__fastcall</b> TForm1<b>:</b><b>:</b>TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b>
<b>:</b> TForm<b>(</b>Owner<b>)</b>
<b>{</b>
<b>}</b>
<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>FormCreate<b>(</b>TObject <b>*</b>Sender<b>)</b>
<b>{</b>
m_foo <b>=</b> <font color="blue">"hello world"</font><b>;</b>
<b>}</b>
</pre>
<P>
If <TT>OldCreateOrder</TT> is true, <TT>FormCreate</TT> will execute before the derived <TT>TForm1</TT> constructor runs, and before the
<TT>m_foo</TT> member variable has been constructred. In this code, <TT>m_foo</TT> is default constructed. This default construction happens just after
the base class constructor is called (ie when <TT>:TForm(Owner)</TT> returns). But <TT>FormCreate</TT> is triggered from inside of the base class
constructor. When the "hello world" assignment executes, <TT>m_foo</TT> hasn't been constructed yet. It essentially does not exist.
</P>
<P>
Assigning values to variables that haven't been constructed is not a good thing. So what happens to this code if <TT>OldCreateOrder</TT> 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 <TT>OldCreateOrder</TT> property.
</p>
<P>
Ok, so let's summarize the most important reason why <TT>OnCreate</TT> 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. <TT>OnCreate</TT> violates this. <TT>OnDestroy</TT> does too, but during destruction.
</P>
<P>
Now, you might be thinking to yourself: "hey they danger isn't <TT>OnCreate</TT>, its that evil <TT>OldCreateOrder</TT> property. As long as
<TT>OldCreateOrder</TT> is false, <TT>OnCreate</TT> and <TT>OnDestroy</TT> are safe." This statement is for the most part correct. While it is
true that you can control the behavior of <TT>OnCreate</TT> and <TT>OnDestroy</TT> through <TT>OldCreateOrder</TT>, it is in fact difficult to
keep control of <TT>OldCreateOrder</TT> itself. <TT>OldCreateOrder</TT> 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, <TT>OldCreateOrder</TT>, 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 <TT>OnCreate</TT> and
<TT>OnDestroy</TT>, and you won't have to worry about <TT>OldCreateOrder</TT> rearing its ugly head.
</P>
<P>
There is another reason to avoid <TT>OnCreate</TT>, even if you have <TT>OldCreateOrder</TT> set properly. It is inefficient to initialize
objects inside of <TT>OnCreate</TT>. In the code above, <TT>m_foo</TT> is default constructed. When the <TT>AfterConstruction</TT> event fires the
<TT>OnCreate</TT> handler, the string "hello world" is copied into <TT>m_foo</TT>. This occurs via the construction of a temporary <TT>AnsiString</TT>
object, followed by a called to <TT>AnsiString</TT>'s assignment operator. This is somewhate ineffiectient. If all we wanted to do was initialize
<TT>m_foo</TT> with "hello world", the most efficient method is to use direct initialization in the constructor. Like this:
</P>
<pre>
<b>__fastcall</b> TForm1<b>:</b><b>:</b>TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b>
<b>:</b> TForm<b>(</b>Owner<b>)</b><b>,</b>
m_foo<b>(</b><font color="blue">"hello world"</font><b>)</b>
<b>{</b>
<b>}</b>
</pre>
<P>
This code initializes <TT>m_foo</TT> using the <TT>char *</TT> conversion constructor of <TT>AnsiString</TT>. 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 of doing things, as opposed to the Delphi way.
</P>
<P>
It is our advice that BCB users pretend that <TT>OnCreate</TT> and <TT>OnDestroy</TT> don't exist. You are using a C++ product, so we feel it is
wise just code things the C++ way.
</P>
<BR>
<H3>
<A NAME="createform">Use <TT>new</TT> instead of Application->CreateForm</A>
</H3>
<P>
When you need to create a form in code, use the <TT>new</TT> operator instead of calling <TT>Application->CreateForm</TT>.
If the IDE puts <TT>CreateForm</TT> in your code, then just leave it alone. This applies primarily to the <TT>WinMain</TT> function
in your project cpp file. But for code that you write, use <TT>new</TT> instead.
</P>
<P>
Here are some differences between <TT>CreateForm</TT> and the <TT>new</TT> operator (5 is the best):
</P>
<P>
<B>1-</B> If your form is the first form being constructed with <TT>CreateForm</TT>, 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 <TT>CreateForm</TT>, then it gets
promoted to be the main form. With <TT>new</TT>, this does not happen.
</P>
<P>
<B>2-</B> <TT>CreateForm</TT> always sets the global application object to be the owner of the form.
You can't override this. With <TT>new</TT>, you get to explicitly pass the owner.
</P>
<P>
<B>3-</B> <TT>CreateForm</TT> 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? It doesn't even know anything about your form's type (ie <TT>TForm1</TT>).
</P>
<P>
The answer is that <TT>CreateForm</TT> 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
<TT>new</TT> instead.
</P>
<P>
<B>4-</B> Because of the virtual constructor stuff, <TT>CreateForm</TT> can only call one type of
form constructor: the constructor that takes a single <TT>TComponent *</TT> for an owner. If
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -