When the runtime environment contains a garbage collection mechanism, it is very important to differentiate between memory management and resource management. Typically, the garbage collector is only interested in the allocation and release of memory containing objects. It does not care whether your object has other resources, such as database connections or core object handles.
Memory Management
Local C ++ provides programmers with direct control over Memory Management. Allocating an object on the stack means that the object will be allocated memory only when it enters a specific function, when the function is returned or the stack is expanded, the memory is released. You can use the new operator to dynamically allocate memory to the object. In this case, the memory is allocated to the CRT heap and the programmer needs to use the delete operator on the object pointer explicitly to release the object. This precise control of memory is also one of the reasons why C ++ can be used to write extremely efficient programs. However, if the programmer is not careful, this is also the cause of Memory leakage. On the other hand, you don't need to turn to the garbage collector to avoid memory leaks-in fact, this is the method adopted by CLR, and it is a very effective method. Of course, for garbage collection, there are also some other advantages, such as the improved allocation efficiency and the advantages related to the reference location. All of this can be implemented through library support in C ++, but in addition, CLR also provides a single memory management programming model, it is common to all programming languages. Think about all the work that needs to be done to interwork with COM automation objects in C ++ and schedule data types, the significance of the garbage collector across several programming languages is very significant.
For efficiency, CLR also retains the concept of stack so that value types can be allocated on it, but CLR also provides a newobj intermediate language instruction to allocate an object in the managed heap, however, this command is only provided when the new operator is used for referenced objects in C. In CLR, there is no function corresponding to the delete operator in C ++. When an application no longer references an object, the allocated memory will be recycled by the garbage collector.
When the new operator is applied to the reference type, the hosted C ++ will also generate the newobj command. Of course, the delete operator is invalid. This is indeed a contradiction, but it also proves that it is not a good practice to use the C ++ pointer to represent a reference type.
In terms of memory management, C ++/CLI does not provide anything new except the content discussed in the object construction section. Resource Management, C ++/CLI.
Resource management
CLR can beat local C ++ only in terms of resource management. Bjarne Stroustrup's technical point of view "resource acquisition is initialization", basically defines the resource type pattern, that is, the class constructor Obtains resources, and the destructor releases resources. These types are treated as partial objects on the stack, or members of complex types. Their destructor automatically release previously allocated resources. As Stroustrup said, "C ++ is the best language for the garbage collection mechanism, mainly because it generates a small amount of garbage. "
Perhaps surprisingly, CLR does not provide any explicit runtime support for resource management. CLR does not support C ++ concepts similar to destructor, but rather. in the. NET Framework, the resource management mode is upgraded to the central location of an IDisposable core interface type. This idea comes from the type of packaging resources. It is reasonable to implement a single Dispose method for this interface so that callers can call this method when they no longer use resources. Needless to say, C ++ programmers will think this is a step backwards in the Times, because they are used to writing the correct code to clean up by default.
Because you have to call a method to release resources, the problem is that it is more difficult to write "no exception" code. Because exceptions may occur at any time, you cannot simply put a call to the Dispose method of the object after a piece of code. In this case, you must take the risk of resource leakage. The solution to this problem in C # is to use try-finally block and using statements to provide a more reliable way to call the Dispose method in the face of exceptions. Sometimes constructors use this method, but in general, you must remember to write them manually. If you forget, the generated code may have a silent error. Whether the try-finally block and using statements are required for languages that lack true destructor remains to be demonstrated.
Using (SqlConnection connection = new SqlConnection ("Database = master; Integrated Security = sspi ")) { SqlCommand command = connection. CreateCommand (); Command. CommandText = "sp_databases "; Command. CommandType = CommandType. StoredProcedure;
Connection. Open ();
Using (SqlDataReader reader = command. ExecuteReader ()) { While (reader. Read ()) { Console. WriteLine (reader. GetString (0 )); } } } |
For C ++ hosting, the plot is very similar. You also need to use a try-finally statement, but it is Microsoft's extension to C ++. Although it is easy to write a simple Using template class to wrap GCHandle, and call the Dispose method of the hosted object in the destructor of the template class, however, the hosted C ++ still does not have the equivalent of the C # using statement.
Using <SqlConnection> connection (new SqlConnection (S "Database = master; Integrated Security = sspi "));
SqlCommand * command = connection-> CreateCommand (); Command-> set_CommandText (S "sp_databases "); Command-> set_CommandType (CommandType: StoredProcedure );
Connection-> Open ();
Using <SqlDataReader> reader (command-> ExecuteReader ());
While (reader-> Read ()) { Console: WriteLine (reader-> GetString (0 )); } |
Consider the traditional support for resource management in C ++, which is also applicable to C ++/CLI, however, the C ++/CLI language is like a breeze for C ++ resource management. First, when writing a class for resource management, for most CLR platform languages, one problem is how to correctly implement the Dispose mode, it is not as easy to implement as the classic destructor in local C ++. When writing the Dispose method, you must make sure that the base class's Dispose method is called. In addition, if you choose to call the Dispose method to implement the class's Finalize method, you must also pay attention to concurrent access because the Finalize method may be called by different threads. In addition, unlike normal program code, if the Dispose method is actually called by the Finalize method, you must release the managed resources carefully.
C ++/CLI is not too far away from the above situation, but it provides a lot of help. Before we can see what it provides, let's take a quick look at how close C # Is to hosting C ++ today. The following example assumes that Base is derived from IDisposable.
Class Derived: Base { Public override void Dispose () { Try { // Release managed and unmanaged Resources } Finally { Base. Dispose (); } }
~ Derived () // implement or overload the Object. Finalize method { // Only release unmanaged Resources } } |
Similar to hosting C ++, it looks like the Destructor code is actually a Finalize method. The compiler actually inserts a try-finally block and calls the Finalize method of the base class. Therefore, C # It is easier to compile a Finalize method than to host C ++, but it does not provide any help when writing the Dispose method. Programmers often use the Dispose method as a pseudo destructor to execute some other code at the end of the code block, rather than releasing any resources.
C ++/CLI recognizes the importance of the Dispose method and makes it a logical "destructor" in the reference type ".
Ref class Derived: Base { ~ Derived () // implement or overload the IDisposable: Dispose method { // Release managed and unmanaged Resources }
! Derived () // implement or overload the IDisposable: Dispose method { // Only release unmanaged Resources } }; |
For C ++ programmers, this makes them feel more natural. They can release resources in destructor as they used. The compiler generates the necessary IL (intermediate language) to correctly implement the IDisposable: Dispose method, including restraining the garbage collector from calling any Finalize method of the object. In fact, explicit implementation of Dispose in C ++/CLI is illegal, and inheritance from IDisposable only leads to a compilation error. Of course, once the type is compiled, all CLI languages that use this type will only see that the Dispose mode is implemented in the most natural way in each of these languages. In C #, you can directly call the Dispose method, or use a using statement-if the type is defined in C. What about C ++? Is it necessary to call the Destructor normally for the objects in the heap? The delete operator is used here. Using the delete operator for a handle will call the Dispose method of this object, and the memory of the recycle object is what the garbage collector should do, we don't need to worry about releasing that part of the memory. Just release the object's resources.
Derived ^ d = gcnew Derived (); D-> SomeMethod () Delete d; |
If a handle is passed to the delete operator in the expression, the Dispose method of the object is called. If no other object is linked to the reference type, the garbage collector releases the memory occupied by the object. If the expression is a local C ++ object, the destructor of the object will be called before the memory is released.
Undoubtedly, in object lifecycle management, we are getting closer and closer to the natural C ++ syntax, but it is not easy to remember to use the delete operator at all times. C ++/CLI allows stack semantics for reference types, which means that you can use a reference type by allocating objects on the stack, the compiler will provide you with the expected C ++ semantics. At the underlying layer, objects are still allocated in the managed heap to meet CLR needs.
Derived d; D. SomeMethod (); |
When d is out of range