Terms 10: Prevent resource leaks in the constructor (on)
If you are developing a contact program with multimedia features. In addition to the usual text information such as name, address, telephone number, this contact can store photos and sounds (you can give correct pronunciation of their name).
In order to achieve this communication, you can design:
Class image {// Used for image data
PUBLIC:
Image (const string & imagedatafilename);
...
}
Class audioclip {// for sound data
PUBLIC:
Audioclip (const string & audiodatafilename);
...
}
Class Phonenumber {...}; // Used to store phone numbers
Class BookenTry {// Reference in Address Book
PUBLIC:
BookenTry (Const String & Name,
Const string & address = "",
Const string & imagefilename = "",
Const string & audioclipfilename = "");
~ BookenTry ();
/ / Add to the phone number through this function
Void Addphonenumber (Const Phonenumber & Number);
...
Private:
String thename; /// human name
String theaddress; // their address
List
Image * theimage; // their image
Audioclip * theaudioclip; // A piece of sound fragment
}
Each entry of the address book has a name data, so you need a constructor with parameters (see Terms 3), but other content (address, image, and sound file name) are optional. Note You should use the Link List (List) to store the phone number, this class is a container class (container class "in the standard C class library (STL). (See Effective C Terms 49 and Terms of this book 35)
Write the BookenTry constructor and the destructor, there is a simple way:
BookenTry :: BookenTry (Const String & Name,
Const String & Address,
Const string & imagefilename,
Const string & audioclipfilename)
: thename (name), THEDRESS (Address),
THEIMAGE (0), THEAUDIOCLIP (0)
{
IF (imagefilename! = ") {
TheImage = new image (imagefilename);
}
IF (AudioclipFileName! = ") {
THEAUDIOCLIP = New Audioclip (AudioclipFileName);
}
}
BookenTry :: ~ Bookentry ()
{
Delete theimage;
Delete theaudioclip;
}
The constructor is initialized to empty, and then if its corresponding constructor parameters are not empty, let these pointers points to real objects. The destructor is responsible for deleting these pointers to ensure that the BookenTry object does not have resource leaks. Because C ensures that the empty pointer is safe, Bookentry's destructor does not need to detect these pointers to point to certain objects before deleting the pointer.
It seems that it seems to be good, it is really good under normal circumstances, but under abnormal conditions (for example, in the abnormal situation), they will not be good.
Please think about if the Bookentry constructor is being executed, an exception is thrown, what happens? :
IF (AudioclipFileName! = ") {
THEAUDIOCLIP = New Audioclip (AudioclipFileName);
}
An exception is thrown, can be because Operator New cannot assign enough memory to AUDIOCLIP, or because Audioclip constructor will throw an exception. Whenever any reason, if an exception is thrown within the Bookentry constructor, this exception will be passed to the place to establish the BookenTry object (outside the constructor. Translator's note).
Now suppose to establish theaudioclip object, an exception is thrown (and the procedure control to the BookenTry constructor outside), then who is responsible for deleting the object you have directed by the THEIMAGE? The answer should be made by Bookentry, but this answer is wrong. Bookentry will not be called at all, never.
C can only delete the full constructed object (Fully Contructed Objects), only one object's constructor is completely run, this object can be completely constructed. So if a Bookentry object B is established as a local object, as follows:
Void TestBookenTryClass ()
{
BookenTry B ("Addison-Wesley Publishing Company",
"ONE JACOB WAY, Reading, MA 01867");
...
}
And in the process of constructing B, an abnormality is thrown, and the destructuring function of B will not be called. And if you try to take an active means to process an exception, you call Delete when an exception occurs, as shown below:
Void TestBookenTryClass ()
{
BookenTry * Pb = 0;
Try {
PB = New BookenTry ("Addison-Wesley Publishing Company",
"ONE JACOB WAY, Reading, MA 01867");
...
}
Catch (...) {// captures all exceptions
Delete PB; // Delete PB, when throwing an abnormal
Throw; // Transfer an abnormality to the caller
}
Delete PB; / / Normal Delete PB
}
You will find that the memory allocated for imagery in the Bookentry constructor is still lost, because if the new operation is not completed successfully, the program does not assign a PB. If the Bookentry constructor throws an exception, the PB will be an null value, so remove it in the Catch block, there is no effect other than letting you feel good. Instead of the RAW BOOKENTRY * with a smart pointer (see Terms 9), it does not work before the New operation is successfully completed. C refuses to call the designer that does not complete the construction operation, not deliberately manufacturing difficulties. The reason is: In many cases, it is meaningless, even harmful. What is the destructive function to do if the destructor is called for an object that does not complete the construction operation? Only the way is to add some bytes in each object to indicate how many steps do to perform the constructor? The destructor is then detected these bytes and determines which operations. Such records slow down the running speed of the destructor and make the size of the object becomes large. C avoids this overhead, but the cost is not automatically deleted objects that are partially constructed. (Similar to this example of compromise in program behavior and efficiency can also see Effective C Terms 13)
Because C is not responsible for clearing the object after the object throws an abnormality in the configuration, you must redesign your constructor to make them clear. The method of frequent use is to capture all exceptions, then perform some clear code, and finally resolve an exception to let it continue to transfer. As shown below, use this method in the Bookentry constructor:
BookenTry :: BookenTry (Const String & Name,
Const String & Address,
Const string & imagefilename,
Const string & audioclipfilename)
: thename (name), THEDRESS (Address),
THEIMAGE (0), THEAUDIOCLIP (0)
{
Try {// This Try Block is newly added
IF (imagefilename! = ") {
TheImage = new image (imagefilename);
}
IF (AudioclipFileName! = ") {
THEAUDIOCLIP = New Audioclip (AudioclipFileName);
}
}
Catch (...) {// captures all exceptions
Delete theimage; // Complete the necessary clear code
Delete theaudioclip;
Throw; // Continue to transfer abnormalities
}
}
Do not worry about non-pointer data members in Bookentry, the data member is automatically initialized before the constructor is called. So if the BookenTry constructor starts executing, the object's THENME, THEADRESS, and Thephones data members have been fully constructed. These data can be seen as a fully constructed object, so they will be released automatically, do not have to intervene. Of course, if the constructor calls of these objects may throw an exception, which constructor must consider capturing exceptions, which allows them to complete the required clear operation prior to continuing delivery.