Objective C ++ Reading Notes (6)

Source: Internet
Author: User

New Year ~ I am busy running around for three days. It's time to return to normal life ......

 

 

Cla08: do not escape exceptions from destructor

Prevent exceptions from leavingdestructors

C ++ is not prohibited, but does not encourage exceptions from destructor. Consider:

Class Widget {
Public:
...
~ Widget () {...} // suppose an exception may be thrown out.
};

Void doSomething ()
{
Std: vector <Widget> v;
...
} // V is automatically destroyed here

When vector v is destructed, it has the responsibility to analyze all Widgets it contains. However, if two Widgets throws an exception during those calls, this is too much for C ++. If two exceptions exist at the same time, if the program is not executed, it will lead to unclear behavior. In this example, there will be an ambiguous behavior. The same situation will also occur if any other container (such as list, set) or TR1 of the standard library is used, or even array. C ++ does not like the destructor to spit out exceptions.

What if your destructor needs to execute an operation that may fail and throw an exception? Assume that a class is used for database connection. To ensure that the customer does not forget to call close () on the DBconnection object, a reasonable idea is to create a class for managing DBConnection resources, and call close in the Destructor:

Class DBConn {// This class is used to manage DBConnection objects
Public: // objects
...
~ DBConn () // make sure that the database connection is always closed
{Db. close ();}
Private:
DBConnection db;
};

It allows users to program like this:

{// Open a block)
DBConn dbc (DBConnection: create ());

// Create a DBConnection and hand it over to the DBConn object for management
... // Use the DBConnection object through the DBConn Interface
} // At the block end point, the DBConn object is destroyed, so the close object is automatically called for the DBConnection object

As long as close is successfully called, everything is fine. However, if this call causes an exception, the DBConn destructor will spread the exception, that is, allow it to exit the destructor. This creates a problem because the Destructor throws a hot potato.

There are two main ways to avoid this problem.

· Terminatethe program: Terminate the program if an exception is thrown by close. Generally, the program is terminated by calling abort.

DBConn ::~ DBConn ()
{
Try {db. close ();}
Catch (...){
Make a running record and write down the call to close failed;
Std: abort ();
}
}

It has the advantage of preventing exceptions from spreading out of The Destructor (which will lead to ambiguous behavior ). That is to say, calling abort can predefine "ambiguous behavior.

· Swallowthe exception: swallowed the exception caused by calling close. In this example, the abort statement is removed in the first method.

Generally, it is a bad idea to swallow an exception because it hides important information about "some actions failed! However, in some cases, swallowing exceptions is more desirable than the risk of premature termination or unclear behavior of a program. The program must be able to run reliably after it encounters an error and ignores it. This can be a feasible choice.

· Destructor should never cause exceptions. If the Destructor calls a function that may throw an exception, the Destructor should capture all exceptions without spreading them or terminating the program.

 

The problem with the above method is that the two cannot respond to the situation that causes the close throw exception.

A better strategy is to redesign the DBConn interface so that the customer has the opportunity to respond to possible problems.

Class DBConn {
Public:
...

Void close () // new function for the customer
{
Db. close ();
Closed = true;
}

~ DBConn ()
{
If (! Closed ){
Try {
Db. close (); // close the connection (if the customer does not do this)

}
Catch (...) {// If the close action fails, record it and end the program or swallow an exception
Make a running record and write down the call to close failed;
...
}
}

Private:
DBConnection db;
Bool closed;
};

In this way, the responsibility for calling close is handed over from DBConn's destructor to DBConn's customers (a "double insurance call" is still included in DBConn's destructor "). If an operation may throw an exception when it fails, and another exception must be handled, the exception must be a function other than the self-destructor. This is because of destructor) exceptions are dangerous, and the risk of premature termination or unclear behavior of the program is always at risk. In this example, asking the customer to call close on their own does not impose a burden on them, but rather gives them a chance to handle errors. They can ignore it and rely on DBConn's destructor to call close. If an error occurs, close does throw an exception and DBConn swallowed the exception or ended the program. The customer has no position to complain. After all, they had the opportunity to handle the problem first, and they chose to give up.

· If the customer needs to respond to exceptions thrown during the running of an operation function, the class should provide a common function (non-destructor) to perform the operation.

 

Cla09: virtual functions are never called during constructor and destructor.

Never call virtual functions duringconstruction or destruction

Important: You should not call the virtual function during the construction or analysis, because such a call will not work as you think and will make you very depressed. As a Java or C # programmer, pay more attention to these terms, because they are different from C ++.

Suppose you have a class inheritance system that simulates stock trading, such as purchasing and selling orders. Such transactions must be audited, so each transaction object is created and a corresponding entry needs to be created in a review log. Below is a seemingly reasonable solution:

Class Transaction {// The base class of all transactions
Public:
Transaction ();

Virtual void logTransaction () const = 0; // make a log record of Different Types
...
};

Transaction: Implementation of Transaction () // base class Constructor
{
...
LogTransaction (); // The final action is to log this transaction
}

Class BuyTransaction: public Transaction {// derived class
Public:
Virtual void logTransaction () const;
...
};

Class SellTransaction: public Transaction {// derived class
Public:
Virtual void logTransaction () const;
...
};

Consider what will happen when executing this line of code:

BuyTransaction B;

Obviously, a BuyTransaction constructor will be called, But first, a Transaction constructor must be called first. The base class component in the object of the derived class is constructed before the component of the derived class is constructed. The last line of the Transaction constructor calls the virtual function logTransaction. The called logTransaction version is the one in Transaction, not the one in BuyTransaction, even if the object type is BuyTransaction. During the construction of the base class, the virtual function never matches the derived class downward.

Root Cause: during the construction of the base class of a derived class object, the object type is a base class rather than a derived class. Not only does the virtual function resolve to the base class, but if the language components (such as dynamic_cast and typeid) of runtime type information are used ), the object will also be treated as the base class type. In this example, when the Transaction constructor is about to initialize the base class of a BuyTransaction object, the object type is Transaction. This is reasonable: the exclusive part of the BuyTransaction of this object has not been initialized, so the safest way is to check that they do not exist. The object does not become a derived class object before the constructor of the derived class starts execution. The same principle applies to destructor.

In the above sample code, the Transaction constructor causes direct calls to a virtual function, which is obvious and easy to see violation of these terms. This violation is so explicit that some compilers will give a warning about it (others will not ).

The problem of calling virtual functions during construction or analysis is not always so easy to detect. If Transaction has multiple constructors, each of them must complete the same work. To avoid code duplication, put the common initialization code, including the call to logTransaction, into an initialization function, called init:

Class Transaction {
Public:
Transaction ()
{Init () ;}// call non-virtual...

Virtual void logTransaction () const = 0;
...

Private:
Void init ()
{
...
LogTransaction (); // call virtual here!
}
};

This code is similar in concept to the previous version, but it is more sinister, because it generally avoids complaints from compilers and connection programs. Actually, virtual is called in the constructor. The only way to avoid this problem is to ensure that your constructor or destructor never calls the virtual function on the created or destructed object, all the functions they call follow the same constraints.

How can we ensure that the correct version of logTransaction is called when an object in the Transaction inheritance system is created? Convert logTransaction in Transaction to a non-virtual function, and then require the derived class constructor to pass necessary information to the Transaction constructor, then the function can safely call the non-virtual logTransaction. As follows:

Class Transaction {
Public:
Explicit Transaction (const std: string & logInfo );

Void logTransaction (conststd: string & logInfo) const;

// Now it is a non-virtual function
...
};

Transaction: Transaction (const std: string & logInfo)
{
...
LogTransaction (logInfo); // currently a non-virtual function
}

Class BuyTransaction: public Transaction {
Public:
BuyTransaction (parameters)
: Transaction (createLogString (parameters ))
{...} // Pass the log information to the base class Constructor
...

Private:
Static std: string createLogString (parameters );
};

In other words, because you cannot use the virtual function to call down the base class construction process, you can let the derived class upload the necessary constructor information to the base class constructor for compensation.

In this example, pay attention to the use of the private static function createLogString in BuyTransaction. Using an auxiliary function to create a value and pass it to the base class constructor is generally more convenient (and more readable) than the data required by passing the initial value column of the member to the base class ). Setting that function to static does not cause accidental access to uninitialized data members of a new BuyTransaction object.

· Do not call virtual functions during construction or destructor, because such calls are never dropped to the derived class (compared to the layer at which the constructor and destructor are currently executed ).

 
From pandawuwyj's column

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.