1. Abnormality generated from private child objects
Several parts, I have always demonstrated some techniques to capture an exception thrown from the constructor of the object. These techniques are processed after leaking out of the constructor. Sometimes the caller needs to know these exceptions, but usually (as in the routines I have adopted) an exception is burst from the caller's private child object. Make the user to care about the "invisible" object indicating the fragility of the design.
In history, the implementation of the constructor of (possibly throwing the usual) does not have a simple and robust solution. Look at this simple example:
#include
Class buffer
{
PUBLIC:
Explicit Buffer (SIZE_T);
~ buffer ();
Private:
Char * p;
}
Buffer :: buffer (size_t const count)
: p (new char [count])
{
}
Buffer :: ~ buffer ()
{
Delete [] P;
}
Static void do_something_with (buffer &)
{
}
int main ()
{
Buffer B (100);
Do_something_with (b);
Return 0;
}
Buffer constructor accepts the number of characters and assigns memory from free space, then initializes Buffer :: P pointing it. If the assignment fails, the New statement in the constructor produces an exception, and the buffer user (here is the main function) must capture it.
1.1 TRY block
Unfortunately, capturing this exception is not easy. Because of the throwing out of the buffer :: buffer, the call to all buffers should be packed in the TRY block. No brain solution:
Try
{
Buffer B (count);
}
Catch (...)
{
Abort ();
}
Do_something_with (b); // error. At this point,
// 'b' no longer exists
Yes no. The call to do_something_with () must be in the TRY block:
Try
{
Buffer B (100);
Do_something_with (b);
}
Catch (...)
{
Abort ();
}
// do_something_with (b);
(So exclusive gossip: I know that call abort () to handle this exception. I just use it to be an example, because now care is to capture an exception instead of processing it.)
Although some is awkward, this method is effective. Then consider this change:
Static Buffer B (100);
int main ()
{
// buffer b (100);
Do_something_with (b);
Return 0;
}
Now B is defined as a global object. Try to put it into the TRY block
Try // um, no, i don't think so
{
Static Buffer B;
}
Catch (...)
{
Abort ();
}
int main ()
{
Do_something_with (b);
Return 0;
}
Will not be compiled.
1.2 exposure implementation
Each example shows the basic defects in the buffer design: the implementation details other than the buffer interface are exposed. Here, the exposure details may fail in the New statement in the buffer constructor. This statement is used to initialize the private child object buffer :: P - a MAIN function and other users cannot operate or even do not know at all. Of course, these users should not be required to pay attention to the abnormality thrown in such sub-objects.
In order to improve the buffer design, we must capture exceptions in the constructor: #include
Class buffer
{
PUBLIC:
Explicit Buffer (SIZE_T);
~ buffer ();
Private:
Char * p;
}
Buffer :: buffer (size_t const count)
: p (null)
{
Try
{
P = new char [count];
}
Catch (...)
{
Abort ();
}
}
Buffer :: ~ buffer ()
{
Delete [] P;
}
Static void do_something_with (buffer &)
{
}
int main ()
{
Buffer B (100);
Do_something_with (b);
Return 0;
}
An exception is included in the constructor. Users, such as the main () function, never know the abnormality, the world is quiet again.
1.3 Members of the constant
Do you do this? Note that the buffer :: p will not be changed again once it is set. In order to avoid the pointer being unintentional, the cautious design is to declare it as const:
Class buffer
{
PUBLIC:
Explicit Buffer (SIZE_T);
~ buffer ();
Private:
CHAR * Const P;
}
Very good, but when it comes this:
Buffer :: buffer (size_t const count)
{
Try
{
P = new char [count]; // error
}
Catch (...)
{
Abort ();
}
}
Once initialized, a constant member cannot be changed, even in a constructor containing their objects. Constant members can only set a list of members that can be initialized by a member of the constructor.
Buffer :: buffer (size_t const count)
: p (new char [count]) // ok
This allows us to return to the paragraph and re-produced the issue we initially want to solve.
OK, how much: Do not initialize the P, and replace the auxiliary function that uses NEW internally:
Char * new_chars (size_t const country)
{
Try
{
Return new char [count];
}
Catch (...)
{
Abort ();
}
}
Buffer :: buffer (int const count)
: p (New_Chars (count))
{
// Try
// {
// p = new char [count]; // error
//}
// catch (...)
// {
// Abort ();
//}
}
This can work, but the cost is an additional function but only to protect an event that does not occur almost no.
1.4 function TRY block
(WQ Note: Later, it will be said that Function TRY blocks cannot block the throwing action of the constructor, which only does the function of exception filtering !!! See P14.3)
I didn't find which one can be indeed satisfactory in these suggestions. What I expected is a language-level solution to handle some of the composition of the sub-objects, and it does not cause the problems mentioned above. Fortunately, the language has just contained such a solution.
After thinking about thinking, the C Standards Committee adds a thing called "Function Try Blocks" to language norms. As a TRY bridge, the function TRY block captures the exceptions in the definition of the entire function, including the member initialization list. Don't be strange, because the language has not been designed to support function TRY blocks, so the syntax is somewhat blame: buffer :: buffer (size_t const count)
Try
: p (new char [count])
{
}
Catch
{
Abort ();
}
It seems that {} behind the usual TRY block is actually a function of the constructor. In the effect, {} has a double role, otherwise, we will face more twisted things:
Buffer :: buffer (int const count)
Try
: p (new char [count])
{
{
}
}
Catch
{
Abort ();
}
(Note: Although nesting {} is redundant, this version can be compiled. In fact, you can nest any weight {} until you encounter the limit of the compiler.)
If there are multiple initialization in the initial list, we must put them in the same function TRY block:
Buffer :: buffer ()
Try
: p (...), q (...), r (...)
{
// Constructor Body
}
Catch (std :: bad_alloc)
{
// ...
}
Like ordinary TRY blocks, there can be any exception handler:
Buffer :: buffer ()
Try
: p (...), q (...), r (...)
{
// Constructor Body
}
Catch (std :: bad_alloc)
{
// ...
}
Catch (int)
{
// ...
}
Catch (...)
{
// ...
}
In addition to the quirky grammar, the function TRY block solves our original problem: all the exceptions thrown from the build function of the buffer sub-object remain in the buffer constructor.
Because we now expect the buffer's constructor without throwing any exception, we should give it an exceptional specification:
Explicit Buffer (SIZE_T) throw ();
Then think about it, we should be a better programmer, so all the functions have been added to the abnormal specifications:
Class buffer
{
PUBLIC:
Explicit Buffer (SIZE_T) throw ();
~ buffer () throw ();
// ...
}
// ...
Static void do_something_with (buffer &) throw ()
// ...
Rounding third and Heading for Home
For example, the final version is:
#include
Class buffer
{
PUBLIC:
Explicit Buffer (SIZE_T) throw ();
~ buffer () throw ();
Private:
CHAR * Const P;
}
Buffer :: buffer (size_t const count)
Try
: p (new char [count])
{
}
Catch (...)
{
Abort ();
}
Buffer :: ~ buffer ()
{
Delete [] P;
}
Static void do_something_with (buffer &) throw ()
{
}
int main ()
{
Buffer B (100);
Do_something_with (b); return 0;
}
Compile with Visual C , sit down and look at the prompt output of the IDE.
Syntax error: missing ';' Before 'TRY'
Syntax error: missing ';' Before 'TRY'
'count': undeclared identifier
'
To be a function definition
Syntax error: missing ';' Before 'Catch'
Syntax error: missing ';' Before '{'
Missing Function HEADER (Old-Style Formal List?)
Oh!
Visual C does not support function TRY blocks. In the compiler I have tested, only Edison Design Group C Front End Version 2.42 considers these code legal.
(By the way, I am particularly concerned about why the compilation will repeat the first error. Maybe it calculates that you will not believe in the first time.)
If you insist on using Visual C , you can use the solution to the TRY block before the introduction. I like to use additional new package functions. If you agree, consider making it a template:
Template
T * new_Array (size_t const country)
{
Try
{
Return New T [count];
}
Catch (...)
{
Abort ();
}
}
// ...
Buffer :: buffer (size_t const count)
: p (New_Array
{
}
This template is much more common than the original new_chars function, and it works on the type other than char. At the same time, it has a concealed anomaly related issue, and I will talk about it next time.