Objective C ++ Reading Notes (8)

Source: Internet
Author: User

Clause 12: Do not forget each of the components of a copy object.
 
Copy all parts of an object
 
In a well-designed object-oriented system, only two functions are encapsulated inside the object for object copying: copy constructor and copy assignment operator, collectively referred to as copy function. The copy function generated by the compiler copies the member variables of the Copied object.
 
Consider a class that represents a customer. The copy function here is manually written to record the call logs for them:
 
Void logCall (const std: string & funcName); // create a log entry
 
Class Customer {
Public:
...
Customer (const Customer & rhs );
Customer & operator = (const Customer & rhs );
...
 
Private:
Std: string name;
};
Customer: Customer (const Customer & rhs)
: Name (rhs. name) // copy rhs data
{LogCall ("Customer copy constructor ");}
 
Customer & Customer: operator = (constCustomer & rhs)
{
LogCall ("Customer copy assignment operator ");
 
Name = rhs. name; // copy rhs data
 
Return * this;
}
 
Everything here looks good, but it is actually good-until another data member is added to the Customer:
 
Class Date {...}; // Date
 
Class Customer {
Public:
... // Same as the previous www.2cto.com
 
Private:
Std: string name;
Date lastTransaction;
};
 
Here, the existing copy functions only make partial copies: they copy the Customer's name, but do not copy its lastTransaction. However, most compilers do not give any warning even at the highest warning level. Conclusion: if you add a data member to a class, you must update the copy function. You also need to update all constructors in the class and any non-standard operator =.
 
In the event of inheritance, it may cause a hidden danger that is the most rampant on this topic. Consider:
 
PriorityCustomer: PriorityCustomer (constPriorityCustomer & rhs)
: Customer (rhs), // call the copy constructor of the base class
Priority (rhs. priority)
{LogCall ("PriorityCustomer copy constructor ");}
 
PriorityCustomer &
PriorityCustomer: operator = (const PriorityCustomer & rhs)
{
LogCall ("PriorityCustomer copy assignment operator ");
 
Customer: operator = (rhs); // assigns a value to the base class.
Priority = rhs. priority;
 
Return * this;
}
 
When writing a copy function for a derived class, you must copy the base class at the same time. These components are often private, so you cannot directly access them. You should have the copy function of the derived class call the corresponding base class function. When writing a copy function, you must ensure that (1) Copy all local data members and (2) Call the appropriate copy function in all base classes.
 
· The copy function should ensure that all data members of an object and all base classes are copied.
 
 
 
In reality, two copy functions often have similar function bodies, which may attract you to try to avoid code duplication by calling one function and the other. The idea of avoiding code duplication is worth noting, but it is wrong to use one copy function to call another.
 
"Using the copy assignment operator to call the copy constructor" and "using the copy constructor to call the copy assignment operator" are meaningless. If you find that your copy constructor has similar code as the copy assignment operator, you can create a third member function called by both to eliminate duplicates. Such a function is private and often called init. This policy eliminates duplicate code in the copy constructor and copy assignment operator, which is secure and proven.
 
· Do not try to implement another function based on one copy function. Instead, place the common functions in the third function for both parties to call.
 
 
 
 
 
 
 
Clause 13: object-oriented resource management
 
Use objects to manage resources
 
Suppose we use a library for modeling Investment behaviors (such as stocks and bonds), and various Investment types inherit from root class investors. Let us further assume that this library uses a factory function to provide us with a specific method for the Investment object:
 
Class Investment {...}; // root class in the "Investment type" Inheritance System
 
Investment * createInvestment ();/* returns a pointer to the dynamically allocated object in the Investment inheritance system. The caller is responsible for deleting it. Here, parameters are deliberately not written for simplicity */
 
When the objects returned by the createInvestment function are no longer used, the caller deletes the objects. The following function f is used to perform the following duties:
 
Void f ()
{
Investment * pInv = createInvestment (); // call the factory object
...
Delete pInv; // release the object specified by pInv
}
 
In the following situations, f may not be able to delete the investment objects from createInvestment:
 
1. The "..." section has a return statement that appears in advance, and the control flow cannot reach the delete statement;
 
2. Use and delete createInvestment in a loop, which exits early with a continue or goto statement;
 
3. Some statements in "..." may throw an exception, and the control flow will no longer reach that delete.
 
Simply relying on "f will always execute its delete statement" won't work.
 
To ensure that the resources returned by createInvestment are always released, we need to put the resources into the object. When the control flow leaves f, the destructor of this object will automatically release those resources. By putting resources inside the object, we can rely on C ++'s "automatic calling of destructor" to ensure that resources are released.
 
Many resources are dynamically allocated to the stack and used in a single block or function, and should be released when the control flow leaves that block or function. The auto_ptr of the standard library is designed for this situation. Auto_ptr is an object similar to a pointer (smart pointer). Its destructor automatically calls delete for the object it refers. The following describes how to use auto_ptr to prevent f's potential resource leakage:
 
Void f ()
{
Std: auto_ptr <Investment> pInv (createInvestment (); // call the factory function
... // Use pInv as before
} // Automatically delete pInv through the destructor of auto_ptr
 
This simple example demonstrates two key ideas of "Object-based resource management:
 
· Put resources into the management object immediately after obtaining them. As shown above, the resources returned by createInvestment are used to initialize the auto_ptr to be used to manage it. In fact, the concept of "Object-based Resource management" Is often referred to as "Resource Acquisition time Is the Initialization time" (Resource Acquisition Is Initialization; RAII ), because we almost always initialize a management object in the same statement after obtaining a resource. Sometimes the obtained resource is assigned to the resource management object (instead of initialization), but both methods immediately hand over the resource management object while obtaining the resource.
 
· Management Objects use their destructor to ensure resources are released. Because when an object is destroyed (for example, when an object leaves its activity range), The Destructor is automatically called, no matter how the control process leaves a block, all resources are correctly released. If the action to release resources causes an exception to be thrown, the issue becomes tricky.
 
When an auto_ptr is destroyed, it will automatically delete what it points to, so do not point more than one auto_ptr to the same object. If this happens, the object will be deleted more than once, and your program will be moved into unclear behavior. To prevent this problem, auto_ptrs has an unusual feature: copying them (by copying constructors or copying assignment operators) sets them to null, the pointer obtained by copying will obtain the unique ownership of the resource!
 
Std: auto_ptr <Investment> pInv1 (createInvestment ());
 
// PInv1 points to the object returned by createInvestment
 
Std: auto_ptr <Investment> pInv2 (pInv1 );
 
// Now pInv2 points to the object, and pInv1 is set to null
 
PInv1 = pInv2; // now pInv1 points to the object, and pInv2 is set to null
 
Resources managed by auto_ptrs must be directed to more than one auto_ptr at the same time, which means auto_ptrs is not the best way to manage all resources dynamically allocated. For example, STL containers require that their elements perform normal replication, so these containers cannot accommodate auto_ptrs.
 
The alternative to auto_ptrs is reference-counting smart pointer (RCSP ). RCSP can continuously track how many objects point to a specific resource and delete it when there is no longer anything pointing to that resource. In this case, RCSP provides behavior similar to garbage collection ). The difference is that RCSP cannot break the circular reference (for example, two objects without other users direct to each other ). TR1: shared_ptr of tr1 is an RCSP:
 
Void f ()
{
...
 
Std: tr1: shared_ptr <Investment> pInv (createInvestment ());
 
// Call the factory function
... // Use pInv as always
} // The pInv is automatically deleted by using the shared_ptr destructor.
 
The code here looks almost the same as the code using auto_ptr, but copying shared_ptrs is more natural:
 
Void f ()
{
...
 
Std: tr1: shared_ptr <Investment> pInv1 (createInvestment ());
 
// PInv points to the createInvestment object
 
Std: tr1: shared_ptr <Investment> pInv2 (pInv1 );
 
// PInv1 and pInv2 point to the same object
 
PInv1 = pInv2; // same as above, no change
...
} // PInv1 and pInv2 are destroyed, and the objects they refer to are automatically destroyed.
 
Because the behavior of copying tr1: shared_ptrs is "as expected", they can be used in STL containers and in other environments that are incompatible with the unorthodox copy behavior of auto_ptr. Auto_ptr and tr1: shared_ptr both use delete in their destructor, instead of delete []. This means that the idea of using auto_ptr or tr1: shared_ptr to dynamically allocate an array is awesome.
 
In C ++, there is no such thing as auto_ptr or tr1: shared_ptr that can be used to dynamically allocate arrays, even in tr1. That's because vector and string can almost always replace dynamic array allocation. You can also look at the Boost, boost: scoped_array and boost: shared_array classes to provide the behavior you are looking.
 
If you manually release resources (for example, using delete instead of resource management), you are in trouble. Pre-fabricated resource management classes such as auto_ptr and tr1: shared_ptr usually make the suggestions in these terms easier, but sometimes the resources you use are currently not properly managed by these pre-fabricated classes, you need to carefully create your own resource management class. Finally, it must be pointed out that raw pointer returned by createInvestment is an invitation for resource leakage, because callers can easily forget to call delete on the pointer they get back. (Even if they use an auto_ptr or tr1: shared_ptr to complete the delete operation, they still have to remember to store the returned values of createInvestment in the smart pointer object ).
 
· To prevent resource leakage, use RAII objects to obtain resources in the RAII object constructor and release them in the destructor.
 
· Two common RAII types are tr1: shared_ptr and auto_ptr. The former is usually a better choice, because its copy behavior is more intuitive. If auto_ptr is selected, the copy Action directs the Copied object to null.
 
 


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.