Template security
Last time, I started to discuss unusual security. This time, I will explore the template security.
The template is instantiated according to the type of parameters. Since I usually don't know the specific type in advance, it is not possible to know where to produce an exception. What you are looking for is to discover where you may have a throw. Such behavior is very challenging.
Look at this simple template class:
Template
Class Wrapper
{
PUBLIC:
Wrapper ()
{
}
T get ()
{
Return Value_;
}
Void Set (T Const & Value)
{
Value_ = value;
}
Private:
T value_;
Wrapper (Wrapper Const &);
Wrapper & Operator = (Wrapper Const &);
}
If the name is indicated, Wrapper is equipped with a T type object. Method GET () and set () get and change the private inclusive object value_. Two common methods - copy constructors and assignment operators are not used, so they are not defined, while the third-destructor is implied by the compiler.
The process of instantiation is simple, for example:
Wrapper
Plume an int. i's definition process causes the compiler to instantiate a class defined as Wrapper
Template <>
Class Wrapper
{
PUBLIC:
Wrapper ()
{
}
int GET ()
{
Return Value_;
}
Void Set (Int Const & Value)
{
Value_ = value;
}
Private:
Int value_;
Wrapper (Wrapper Const &);
Wrapper & Operator = (Wrapper Const &);
}
Because Wrapper
1.1 Parameters of Class Types
Watch now:
Wrapper
Here X is a class. In this definition, the compiler instantiates class Wrapper
Template <>
Class Wrapper
{
PUBLIC:
Wrapper ()
{
}
X gET ()
{
Return Value_;
}
Void Set (X Const & Value)
{
Value_ = value;
}
Private:
X value_;
Wrapper (Wrapper Const &);
Wrapper & Operator = (Wrapper Const &);
}
Look at it, there is no problem with this definition, and it has not touched an exception. But thinking about:
l Wrapper
l Wrapper
In Wrapper
Due to such uncertainties, we need to adopt conservative strategies: assuming that Wrapper will instantiate according to the class, and these classes have no abnormal specifications on their members, they may be throwing.
1.2 makes inclusive safety
It is assumed that Wrapper's exception specifications have promised that their members do not produce an exception. At least, we must add exceptional standards in its members (). We need to repair these possibilities that may lead to an exception:
l Construct the Value_ from Wrapper :: Wrapper ().
l Returns the process of Value_ in Wrapper :: get ().
l The process of assigning values to Value_ in Wrapper :: Set ().
In addition, we have to deal with std :: unexpected when violating the exception specifications of throw ().
1.3 Leak # 1: Default Constructor
For the default constructor of Wrapper, the solution looks uses the Function TRY block:
Wrapper () throw ()
TRY: T ()
{
}
Catch (...)
{
}
Although it is very attractive, it can't work. According to C standards (Paragraph 15.3 / 16, "Handling An Exception":
Function-Try-Block on the constructor or destructive function, when the control arrives at the end point of the abnormality processing function, the captured exception is thrown again. For a general function, this is the function returns, equivalent to the RETURN statement that does not return value, and the behavior of the function defined for the return type This time is undefined.
In other words, the above program is equivalent to:
X :: x () throw ()
TRY: T ()
{
}
Catch (...)
{
Throw;
}
This is not what we want.
I have thought about this:
X :: x () throw ()
Try
{
}
Catch (...)
{
Return;
}
But it violates the standard Paragraph 15:
If a Return statement occurs in the exception handler of the Function-Try-Block on the constructor, the program is illustrative.
I was killed by the standard card. After using the compiler test to support the Function TRY block, I didn't find the way to run in the way I expected. No matter how I tried, all captured exceptions were still thrown again, violating the abnormal specifications of throw (), and defeated the target of the security of the interface.
Principle: Unable to use the function try block to implement the interface security of the constructor.
引原 原原 1: Use the constructor to use the textual class or member object as much as possible.
Quarantine 2: In order to help others implement the primary ICAM 1, don't throw any exceptions from your constructor. (This is contradictory with the views I mentioned in Part13.)
I found that the rules of the C standard are very strange because they reduce the actual value of the Function Try: capture from the sub-object (T :: t ()) constructor before entering the constructive function (Wrapper :: wrapper ()) Throwing exception. In fact, the Function Try block is the only way you capture such an exception; however, you can only capture them but can't handle them! (WQ Note: The following text is originally on Part 15, I put it in advance.
The last time I discussed the limitations of the Function TRY block and promised to explore the cause. Nothing from the industry experts I know the exact answer. The only consensus now is:
L As I guess, the Standards Committee designed the Function TRY block as a filtering rather than the abnormality that occurred in the capture sub-object constructor.
l Possible motivation is: Make sure no one misuses an inclusive object that is successful.
I wrote to Herb Sutter, "TEH EXCEPTIONAL C " author. He never touched this problem, but very interested in writing it to the "Guru of The Week" column. If you want to join this discussion, go to News Group comp.lang.c . Moderated "GURU of The Week # 66: Constructor Failures".
)
Note that Function TRY can map or convert exceptions:
X :: x ()
Try
{
Throw 1;
}
Catch (int)
{
Throw 1L; // map int exception to long Exception
}
That look, they are very like UNEXPECTED abnormal processing functions. In fact, I will now suspect that this is their design purpose (at least for constructor): It is more like an abnormal filter instead of an exception handler. I will continue to study to discover the principles behind these rules.
Now, at least, we are forced to use a non-direct solution:
Template
Class Wrapper
{
PUBLIC:
Wrapper () throw ()
: value_ (NULL)
{
Try
{
Value_ = new t;
}
Catch (...)
{
}
}
// ...
Private:
T * value_;
// ...
}
The contained object is originally constructed in Wrapper :: wrapper (), and is now constructed in its function. This change allows us to use ordinary methods to capture exceptions without using the Function Try block.
Because Value_ is now a T * rather than T object, get () and set () must use the syntax of the pointer:
T get ()
{
Return * Value_;
}
Void Set (T Const & Value)
{
* Value_ = Value;
}
1.4 Leak # 1a: Operator New
In the TRY block in the constructor, the statement
Value_ = new t;
The Operator New is invincible to assign * Value_'s memory. And this Operator New function may be throwing.
Fortunately, our wrapper :: wrapper () can capture the T's constructor and an exception thrown by the Operator New function, so that interface security is maintained. However, remember this difference in key:
l If the Tribus constructor throws an exception, Operator delete is implicitly called to release the allocated memory. (For Placement New, depending on whether there is a matching Operator Delete, I said in Part 8 and 9.) L If the Operator New throws an exception, Operator delete will not be invoified.
The second point should not have any problem: If the operator new is abnormal, it is usually because the memory allocation fails, the Operator delete doesn't need it to release. However, if the Operator New successfully assigns memory but still throws an exception because of other reasons, it must be responsible for release memory. In other words, Operator New must be active.
(The same problem also occurs when creating an array via Operator NWe [].)
1.5 Leak # 1b: Destructor
Want Wrapper behavior security, we need its designer function to release New's memory:
~ wrapper () throw ()
{
DELETE VALUE_;
}
This looks very simple, but please wait for the big words! DELETE VALUE_ Calls * Value_ 's destructor, and this destructor may be throwing. To achieve ~ Wrapper () interface exception, we must add TRY block:
~ wrapper () throw ()
{
Try
{
DELETE VALUE_;
}
Catch (...)
{
}
}
But this is not enough. If the destruction function of * Value_ throws an exception, Operator delete will not be called to release * Value_'s memory. We need to add behavior security:
~ wrapper () throw ()
{
Try
{
DELETE VALUE_;
}
Catch (...)
{
Operator delete (Value_);
}
}
Still not ending. The C standard running library declared Operator Delete as
Void Operator Delete (Void *) throw ();
It does not leave ancient, but the custom Operator delete does not say. To be super security, we should write:
~ wrapper () throw ()
{
Try
{
DELETE VALUE_;
}
Catch (...)
{
Try
{
Operator delete (Value_);
}
Catch (...)
{
}
}
}
But this is still dangerous. Statement
DELETE VALUE_;
Implicit calls Operator Delete. If it throws an exception, we will enter the Catch block, step by step and call the same Operator Delete! We continuously expose the same abnormality. This will not be a good program.
Finally, remember: Operator delete is implicitly called when the constructor of the new object is throwing up. If this hidden calling Operator Delete also throws an exception, the program will be in two abnormal states and call Terminate ().
Principle: Do not throw it in a function that may be called by an exception is being processed. In particular, do not throw it from the following cases:
l Destructors
l Operator delete
l Operator delete []
Several small exercises: Instead of Value_, then rewrite the Wrapper constructor and determine the role of its fiction function (if needed), the condition is necessary to keep an unusual security. 1.6 external words
I am preparing to maintain an abnormal safety once. But now is the second part, and still have enough material to write into a third part (I swear is the last part). Next time, I will discuss the abnormal security issues on GET () and set (), and the same is the same as today.