Terpsis 10: Prevent resource leakage in the constructor (below)
You may have noticed the statement in the Catch block of the BookenTry constructor to almost the same statement. The code repeat here is absolutely unable tolerance, so the best way is to move the general code into a private helper function, allowing the constructor to call it with the destructor.
Class BookenTry {
PUBLIC:
... //
Private:
...
Void cleanup (); // universal clear code
}
Void BookenTry :: Cleanup ()
{
Delete theimage;
Delete theaudioclip;
}
BookenTry :: BookenTry (Const String & Name,
Const String & Address,
Const string & imagefilename,
Const string & audioclipfilename)
: thename (name), THEDRESS (Address),
THEIMAGE (0), THEAUDIOCLIP (0)
{
Try {
... //
}
Catch (...) {
Cleanup (); // Release Resources
THROW; // Transfer abnormalities
}
}
BookenTry :: ~ Bookentry ()
{
Cleanup ();
}
This is OK, but it does not take into account the following situation. Suppose we are slightly changed, letnts and Thetioclip are constant pointer types:
Class BookenTry {
PUBLIC:
... //
Private:
...
Image * const theimage; // Pointer is now
Audioclip * const theaudioclip; // const type
}
Such a pointer must be initialized by a member of the Bookentry constructor, because there is no other place to assign a value to the Const pointer (see Effective C Terms 12). It is usually to initialize theImage and THEAUDIOCLIP:
// Implementation method that may lead to resource leakage during exception
BookenTry :: BookenTry (Const String & Name,
Const String & Address,
Const string & imagefilename,
Const string & audioclipfilename)
: thename (name), THEDRESS (Address),
Theimage (ImageFileName! = ""
? new image (imagefilename)
: 0),
THEAUDIOCLIP (AudioclipFileName! = ""
? New AudioClip (AudioclipFileName)
: 0)
{}
Doing so, the problem we have always wanted to avoid: If theaudioclip is initialized, an exception is thrown, and the object referred to theImage will not be released. And we can't solve the problem by adding TRY and CATCH statements in the constructor, because try and catch are statements, and members initialization tables are only allowed to have expressions (this is why we must use in the initialization of TheImage and THEAUDICLIP?: Reason for if-dam-else). In any case, the only way to complete the clearance work before abnormal delivery is to capture these exceptions, so if we can't put TRY and CATCH statements in the member initialization table, we move them elsewhere. One may be in a private member function, returning the pointer with these functions, pointing to the initialized TheImage and theaudioclip object.
Class BookenTry {
PUBLIC:
... //
Private:
... // Data membership
Image * InitImage (const string & imagefilename);
Audioclip * InitaudioClip (Const String &
AudioclipFileName);
}
BookenTry :: BookenTry (Const String & Name,
Const String & Address,
Const string & imagefilename,
Const string & audioclipfilename)
: thename (name), THEDRESS (Address),
InitImage (IMAGEFILENAME),
THEAUDIOCLIP (Initaudioclipfilename))
{}
// Theimage is initially initialized, so even this initialization failed
// Don't worry about resource leakage, this function does not have to be processed.
Image * BookenTry :: InitImage (const string & imagefilename)
{
IF (ImageFileName! = ") Return New Image (ImageFileName);
Else Return 0;
}
// Theaudioclip is initialized by the second, so if you are in THEAUDIOCLIP
// Throw an exception during the initialization process, it must ensure that the resource of TheImage is released.
// So this function uses Try ... catch.
Audioclip * BookenTry :: Initaudioclip (Const String &
AudioclipFileName)
{
Try {
IF (AudioclipFileName! = ") {
Return New Audioclip (AudioclipFileName);
}
Else Return 0;
}
Catch (...) {
Delete theimage;
Throw;
}
}
The above program is really good, but also solves the problem of our headache. However, there is also a shortcompination, in principle, the code that should be a constructor is scattered in several functions, which makes us difficult to maintain.
A better solution is to adopt the recommendations of the Terms 9, and the objects pointing to The Image and THEAUDICLIP are used as a resource and managed by some partial objects. This solution is based on such a fact: TheImage and THEAUDIOCLIP are two pointers, pointing to dynamically assigned objects, so these objects should be deleted when the pointer disappears. The Auto_PTR class is based on this purpose. (See Terms 9) Therefore, we change the THEIMAGE and Theaudioclip Raw pointer type to the corresponding auto_ptr type. Class BookenTry {
PUBLIC:
... //
Private:
...
Const auto_ptr
Const auto_ptr
}
This makes the Bookentry's constructor can do not leak resources even in the abnormal conditions, and let us initialize TheImage and THEAUDIOCLIP using member initialization tables, as shown below:
BookenTry :: BookenTry (Const String & Name,
Const String & Address,
Const string & imagefilename,
Const string & audioclipfilename)
: thename (name), THEDRESS (Address),
Theimage (ImageFileName! = ""
? new image (imagefilename)
: 0),
THEAUDIOCLIP (AudioclipFileName! = ""
? New AudioClip (AudioclipFileName)
: 0)
{}
Here, if you throw an exception while initializing theaudioclip, TheImage is already a fully constructed object, so it can be automatically deleted, just like the thename, theaddress, and thephones. And because theImage and theaudioclip are now included in Bookentry, they can be automatically deleted when Bookentry is deleted. Therefore, it is not necessary to manually delete the objects you point to. It can simplify the destructor of Bookentry:
BookenTry :: ~ Bookentry ()
{} // Nothing to do!
This means that you can completely remove the destructor of Bookentry.
In summary, if you use the corresponding auto_ptr object to replace the pointer member variable, you can prevent the constructor from leakage in the existence of anomalies, you don't have to release resources in the destructive function, and you can use it before The Const pointer uses a Const pointer to assign it.
In the object construct, it is a tricky problem that processes various throwing abnormalities, but Auto_PTR (or class similar to Auto_PTR) can be simple. It not only hides the bad understanding, but also allows the program to remain normally in the face of abnormalities.