C ++ nutrition (RAII)

Source: Internet
Author: User
C ++ nutrition
Mo Huafeng
Animals take up food and absorb the nutrients for their own growth and activity. However, not all substances in food can be absorbed by animals. Substances that cannot be digested are discharged from the body through the other end of the gastrointestinal tract (only one of some animals. However, the excrement that an animal cannot digest is the food of another animal (CREATURE), from which the latter can take the necessary nutrients.
A programming language, for programmers, contains the necessary nutrients just like food. Of course, it also contains something that cannot be digested. The difference is that as programmers grow, they will gradually digest things that they could not digest in the past.
C ++ can be seen as a complex food, which cannot be completely digested by most programmers. Because of this, many Programmers think that C ++ is too hard to digest and should not eat it. However, it is a great sin to give up C ++ nutrition without exploiting it. Fortunately, food can be processed to make it easy to absorb, such as fermentation. In view of the differences in the digestion ability of programmers and to make the nutrition of C ++ benefit others, I will play a yeast for the moment and extract some nutrition of C ++ separately, and break it down to make those programmers with low digestion ability enjoy its delicious taste. :)
(To make these nutrients easy to digest, I will use C # For some cases. The reason for choosing C # is simple, because I am familiar with it. :))
RAII
RAII, weird nutrition! Its full name should be "Resource Acquire Is Initial ". This is a word invented by B jarne Stroustrup, founder of C ++. The meaning of RAII is not complicated. In other words, the class constructor allocates resources and releases resources in the destructor. In this way, when an object is created, the constructor is automatically called. When the object is released, the Destructor is also called automatically. Therefore, an object will no longer occupy resources after its life cycle, and its usage is secure and reliable.
The following is a typical code for implementing RAII in C ++:
Class file
{
Public:
File (string const & name ){
M_fileHandle = open_file (name. cstr ());
}
~ File (){
Close_file (m_fileHandle );
}
...
Private:
Handle m_fileHandle;
}
A typical example is "get in the constructor and release in the Destructor ". If I write the code:
Void fun1 ()...{
File myfile ("my.txt ");
... // Operation File
} // Destroy the object, call the destructor, and release the resource.
When the function ends, the lifecycle of the partial object myfile ends, the Destructor is called, and the resources are released. In addition, if the code in the function throws an exception, the Destructor will be called and the resources will be released. Therefore, in RAII, not only resource security, but also exception security.
However, in the following code, resources are not secure, even though RAII is implemented:
Void fun2 ()...{
File pfile = new file ("my.txt ");
... // Operation File
}
Because we have created an object (via new) on the stack, but it has not been released. We must use the delete operator to release it explicitly:
Void fun3 ()...{
File pfile = new file ("my.txt ");
... // Operation File
Delete pfile;
}
Otherwise, not only the resources in the object cannot be released, but the memory of the object itself cannot be recycled. (GC (garbage collection) will be introduced in the C ++ standard in the future, but GC still cannot ensure resource security as analyzed below ).
Currently, in fun3 (), resources are secure, but not exceptionally secure. Because once an exception is thrown in the function, the delete pfile; Code will not be executed. C ++ experts warned us that if you want to ensure resource security and exceptional security without GC, use smart pointers:
Void fun4 ()...{
Shared_ptr <file> spfile (new file ("my.txt "));
... // Operation File
} // The (delete) object will be released when spfile ends its lifecycle.
So how can we achieve smart pointers? The following code tells you the tricks (for more information about smart pointers, see std: auto_ptr, boost or tr1 smart pointers ):
Template <typename T>
Class smart_ptr
...{
Public:
Smart_ptr (T * p): m_ptr (p )...{}
~ Smart_ptr ()... {delete m_ptr ;}
...
Private:
T * m_ptr;
}
Yes, RAII. That is to say, the smart pointer uses RAII to ensure the security of memory resources, and indirectly implements the RAII on the object. However, the RAII here is not very strict: the creation (resource acquisition) of objects (occupied memory is also a resource) is performed outside the constructor. Broadly speaking, we also classify it into the RAII category. However, in Imperfect C ++, Matthew Wilson called it RRID (Resource Release Is Destruction ). The implementation of RRID requires a contract between class developers and users to acquire and release resources in the same way. For example, if you use malloc () during shared_ptr construction, a problem occurs because shared_ptr releases objects through delete.
For languages with built-in GC, resource management is relatively simple. However, this is not always the case. The following C # code is taken from the C # programming guide of the MSDN Library. I made a slight Transformation:
Static void CodeWithoutCleanup ()
...{
System. IO. FileStream file = null;
System. IO. FileInfo fileInfo = new System. IO. FileInfo ("C:" file.txt ");
File = fileInfo. OpenWrite ();
File. WriteByte (0xF );
}
Will the resource be leaked? This depends on the implementation of the object. If the FileStream object obtained through OpenWrite () is released in the destructor, the resource will not be leaked. GC will call the Finalize () function when performing the GC operation (the destructor of the C # class will be implicitly converted to the overload of the Finalize () function ). This is because C # uses the reference semantics (strictly speaking, it uses the reference semantics for the reference type). An object is actually not an object, but an object reference. As in C ++, references do not release objects when they leave the scope. Otherwise, an object cannot be directly passed out of the function. In this case, resources will not be immediately released if Close () is not explicitly called. However, files, locks, database links, and other resources are important or scarce resources. When GC is executed for recovery, insufficient resources may occur. What's more, it may cause code execution problems. I have encountered such a problem: I executed an SQL operation, obtained a result set, and then executed the next SQL statement. The result cannot be executed. This is because the SQL Server 2000 I use does not allow two result sets to be opened simultaneously on a data connection (this is true for many database engines ). The first result set is not released immediately after it is used up, and the GC operation is not started yet. Therefore, a new SQL statement cannot be executed on a data connection that does not close the result set.
Therefore, as long as resources outside the memory are involved, they should be released as soon as possible. (Of course, it would be better if the memory can be released as soon as possible ). For the preceding CodeWithoutCleanup () function, the Close () function on the file object should be called to release the file:
Static void CodeWithoutCleanup ()
...{
System. IO. FileStream file = null;
System. IO. FileInfo fileInfo = new System. IO. FileInfo ("C:" file.txt ");
File = fileInfo. OpenWrite ();
File. WriteByte (0xF );
File. Close ();
}
Currently, this function is strictly resource-safe, but not strictly exception-safe. If an exception is thrown during file operations, Close () members cannot be called. In this case, the file cannot be closed in time until GC is complete. To this end, you need to handle the exception:
Static void CodeWithCleanup ()
...{
System. IO. FileStream file = null;
System. IO. FileInfo fileInfo = null;
Try
...{
FileInfo = new System. IO. FileInfo ("C:" file.txt ");
File = fileInfo. OpenWrite ();
File. WriteByte (0xF );
}
Catch (System. Exception e)
...{
System. Console. WriteLine (e. Message );
}
Finally
...{
If (file! = Null)
...{
File. Close ();
}
}
}
Try-catch-finally is the standard statement to handle this situation. However, it is much more complicated than the previous C ++ code fun1 () and fun4. There are no consequences for RAII. Next, let's take a look at how to get RAII in C.
A valid RAII should contain two parts: resource acquisition/release of constructor/destructor and deterministic destructor call. The former is not a problem in C #, and C # has constructor and destructor. However, the constructor and destructor of C # cannot be used in RAII. The reason will be seen later. The correct method is to let a class implement the IDisposable interface and release resources in the IDisposable: Dispose () function:
Class RAIIFile: IDisposable
...{
Public RAIIFile (string fn )...{
System. IO. FileInfo fileInfo = new System. IO. FileInfo (fn );
File = fileInfo. OpenWrite ();
}

Public void Dispose ()...{
File. Close ();
}

Private System. IO. FileStream file = null;
}
Next, make sure that the file is permanently released when it is out of scope or when an exception occurs. This task needs to be implemented using the C # using statement:
Static void CodeWithRAII ()
...{
Using (RAIIFile file = new RAIIFile ("C:" file.txt "))
...{
... // Operation File
} // File release

}

Once you leave the using scope, file. Dispose () will be called and the file will be released, even if an exception is thrown. Compared to the messy and complex code in CodeWithCleanup (), CodeWithRAII () can be regarded as pleasing to the eye. More importantly, code conciseness and rules will greatly reduce the possibility of errors. It is worth noting that the using statement can only act on classes that implement the IDisposable interface, even if the Destructor is implemented. Therefore, for classes that require RAII, IDisposable must be implemented. Generally, all classes involving resources should implement this interface for future use. In fact, many classes in the. net Library related to non-memory resources all implement IDisposable and can be directly implemented by using.
However, another problem that using cannot solve is how to maintain the RAII of the class member function. We want a class member object to obtain resources when the class instance is created, and release resources when it is destroyed:
Class X
...{
Public:
X (): m_file ("c: \ file.txt ")...{}
Private:
File m_file; // call File ::~ File () to release resources.
}
But it cannot be implemented in C. Because the instantiated objects in uing are released when they leave the using domain, they cannot be used in constructors:
Class X
...{
Public X ()...{
Using (m_file = new RAIIFile ("C: \ file.txt "))
...{
} // M_file is released, and m_file points to invalid resources.
}
Pravite RAIIFile m_file;
}
RAII of member objects can only be manually released in the Destructor or Dispose. I have not come up with a better solution.
Now, the ins and outs of RAII have been clearly stated, and sufficient nutrients can be obtained from C. However, this is not all about RAII, and RAII has more extensions. In Imperfect C ++, Matthew Wilson demonstrated a very important application of RAII. In order not to get lost in the reputation of a parrot, here is a real case. It is very simple: the program I write needs to respond to the CellTextChange event of a Grid control and execute some operations. In the process of responding to this event (executing an operation), you cannot respond to the same event until the processing is complete. For this reason, I set a flag to control the Event Response:
Class MyForm
...{
Public:
MyForm (): is_cacul (false )...{}
...
Void OnCellTextChange (Cell & cell )...{
If (is_cacul)
Return;
Is_cacul = true;
... // Execute the computing task
Is_cacul = false;
}
Private:
Bool is_cacul;
};
However, the code here is not exceptionally secure. If an exception is thrown during computing, the is_cacul flag will always be true. After that, even normal CellTextChange cannot get the correct response. Like the previous resource problems, we have to resort to try-catch statements. However, if we use RAII, the code can be simplified to being unsimplified and secure to being unsecure. First, I made a class:
Class BoolScope
...{
Public:
BoolScope (bool & val, bool newVal)
: M_val (val), m_old (val )...{
M_val = newVal;
}
~ BoolScope ()...{
M_val = m_old;
}

Private:
Bool & m_val;
Bool m_old;
};
This class is called "scoping". The constructor accepts two parameters: the first is a reference to a bool object, which is saved in the m_val member in the constructor; the second is the new value, which will be assigned to the passed bool object. The original value of this object is stored in the m_old member. The Destructor returns the m_old value to m_val, that is, the bool object. With this class, you can elegantly obtain exceptional security:
Class MyForm
...{
Public:
MyForm (): is_cacul (false )...{}
...
Void OnCellTextChange (Cell & cell )...{
If (is_cacul)
Return;
BoolScope bs _ (is_cacul, true );
... // Execute the computing task
}
Private:
Bool is_cacul;
};
Okay, the task is completed. When bs _ is created, the is_cacul value is replaced with true, and its old value is stored in bs _ object. When OnCellTextChange () is returned, the bs _ object is automatically destructed, And the Destructor automatically assigns the saved original value to is_cacul. Everything goes back to its original form. Similarly, if an exception is thrown, the value of is_cacul will be restored.
This BoolScope can be used in the future, and the development cost is almost zero. Furthermore, you can develop a general Scope template for all types, as in Imperfect C ++.
Next, let's move the battlefield to C # To see how C # implements domain guard. Considering the characteristics of the C # (. net) object model, we first implement the domain guard of the reference type, and then look at how to deal with the value type. The cause will be seen later.
I used to fill in data to a grid, but during the filling process, the control is constantly refreshed, causing flickering and affecting performance, unless the AutoDraw attribute on the control is set to false. To this end, I have implemented a domain guard class. Close AutoDraw before entering the operation, and enable AutoDraw when the operation is complete or an exception is thrown:
Class DrawScope: IDisposable
...{
Public DrawScope (Grid g, bool val )...{
M_grid = g;
M_old = g-> AutoDraw;
M_grid-> AutoDraw = val;
}
Public void Dispose ()...{
G-> AutoDraw = m_old;
}
Private Grid m_grid;
Private bool m_old;
};
As a result, I can handle the AutoDraw attribute settings elegantly as follows:
Static void LoadData (Grid g )...{
Using (DrawScope ds = new DrawScope (g, false ))
...{
... // Execute Data Loading
}
}
Now, let's look back and implement value-type domain guard. The preceding CellTextChange event is used in the case. When I tried to implement domain guard for that is_cacul, I encountered a lot of trouble. At first, I wrote the following code:
Class BoolScope
...{
Private ??? M_val; // What type is used here?
Private bool m_old;
};
M_val should be a reference to an object. C # does not have the pointers and references of C ++. In C #, the object of the reference type definition is actually a reference pointing to the object, while the object of the value type definition is actually an object, or a "Stack object ", however, there is no reference pointing to the value type. (I will discuss the advantages and disadvantages of this object model in the "digression" section ). I tried two methods, one being unsuccessful and the other being successful.
C # (. net) has a box mechanism to package a value object and create it in the heap. In this way, a value object may be programmed to reference an object to form something C # Can reference:
Class BoolScope: IDisposable
...{
Public BoolScope (object val, bool newVal )...{
M_val = val; // #1
M_old = (bool) val;
(Bool) m_val = newVal; // #2
}
Public void Dispose ()...{
(Bool) m_val = m_old; // #3
}
Private object m_val;
Private bool m_old;
}
Use the following format:
Class MyForm
...{
Public MyForm ()...{
Is_cacul = new bool (false); // boxing
}
...
Void OnCellTextChange (Cell & cell )...{
If (is_cacul)
Return;
Using (BoolScope bs = new BoolScope (is_cacul, true ))
...{
... // Execute the computing task
}
}
Private object is_cacul;
};
Unfortunately, this cannot be achieved. Because in code #1, the reference semantics is not executed, but the value semantics is executed. That is to say, the value of val (which is a reference) is not assigned to m_val (which is also a reference), but a copy of m_val. So that newVal and m_old cannot be granted to val (is_cacul) in code #2 and #3 ). Maybe the designers of C # have countless reasons to explain the rationality of this design, but here, they have killed a very useful idom. In addition, the lack of reference methods for value objects greatly limits the flexibility and scalability of the language.
The second method is very straightforward, and there should be no problems, that is, the use of packaging:
Class BoolVal
...{
Public BoolVal (bool v)
...{
M_val = v;
}
Public bool getVal ()...{
Return m_val;
}
Public void setVal (bool v )...{
M_val = v;
}
Private bool m_val;
}
Class BoolScope: IDisposable
...{
Public IntScope (BoolVal iv, bool v)
...{
M_old = iv. getVal ();
M_Val = iv;
M_Val.setVal (v );
}
Public virtual void Dispose ()
...{
M_Val.setVal (m_old );
}
Private BoolVal m_Val;
Private bool m_old;
}

Here, I made a packaging class BoolVal, which is a reference class. Then, a BoolScope class is written based on this. Then, the domain guard can be used normally:
Class MyForm
...{
Public MyForm ()...{
M_val.setVal (false); // boxing
}
...
Void OnCellTextChange (Cell & cell )...{
If (is_cacul)
Return;
Using (BoolScope bs = new BoolScope (m_val, true ))
...{
... // Execute the computing task
}
}
Private BoolVal m_val;
};
Okay, everything is good. Although the object model of C # makes us a lot of trouble and I write a lot of code, it is still a great deal to use the domain guard class. As a GP fans, I certainly try to make some generics in C # To avoid the annoyance of repeatedly developing packaging and domain guard classes. Let's leave these things for you to practice. :)
In some cases, we may perform some operations on some objects, and restore the original state of the object after completion. This is also the application of the domain guard class. It is not easy to guard a complex class. The most direct way is to retrieve all the member data and copy it back after the end. This is of course complicated and inefficient. However, we will see in the next article that if we use the swap technique and the copy constructor, this domain guard can be easily implemented. Let's talk about it later.
As an extended application of RAII, domain guard is simple but practical. If we promote the concept of "resource" and include some values, statuses, and other content into the category of resources, the use of domain guard classes is a matter of course.

Digress: Object Model of C # 
C # is designed to simplify language learning and usage. However, in the case of problems in the previous case, C # tends to be unsatisfactory under specific circumstances, especially when flexibility and scalability are required. The object model of C # is actually centered on heap objects and reference semantics. However, considering the huge overhead and performance loss of maintaining the heap object, the application can be of some simple types, such as int and float. Therefore, C # processes these simple types directly as values, and of course allows users to define their own value types. The value type has the value semantics. The value type is essentially a stack object, and the reference type is a heap object.
This seems like a good compromise, but it actually caused a lot of trouble. The previous case clearly shows the troubles caused by this object model. Because C # discards the differences between values and references (to simplify language learning and usage), we cannot access a referenced object using value semantics. For a value object, we cannot use reference semantics for access. For the former, there is no essential problem, because we can use member functions to implement value semantics. However, the latter is an insurmountable obstacle, as shown in the BoolScope case. In this case, we have to use the reference class to wrap the value type, so that the value type loses its original performance and resource advantages.
What's more, the object model of C # sometimes causes semantic conflicts. Because the value type uses the value semantics, the reference type uses the reference semantics. The same object definition may use different semantics:
Int I, j = 10; // Value Type
I = j; // value semantics, copying content of two objects
I = 5; // I = 5, j = 10
StringBuilder s1, s2 = new StringBuilder ("s2"); // reference type
S1 = s2; // reference semantics. s1 and s2 point to the same object.
S1.Append ("is s1"); // s1 = s2 = "s1 is s2"
The same form has different semantics, which often causes unexpected problems. For example, at the very beginning of software development, we thought that a value type would be sufficient to achieve performance benefits. However, as the project enters the later stage, it is found that there is a problem with the initial design. The value type limits some features of this type (for example, it cannot have destructor or reference ), you need to change it to the reference type. This causes a lot of trouble. You need to check all the code that uses this type, and then change the value assignment operation to the copy operation. This is definitely not a flattering job. Therefore, in actual development, there are few custom value types to avoid future self-binding. Therefore, in addition to the built-in language type and the pre-defined type of the. net library, the value type has become a decoration.
In contrast, traditional languages, such as Ada, C, C ++, and Pascal, distinguish between references and values. Although beginners need to spend more time understanding the differences, however, it is more secure and secure in use. After all, learning is temporary, and usage is always.

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.