The .NET Approach


Just as Sun Microsystems did with Java, Microsoft decided that the disadvantages of manual memory allocation far outweighed the advantages. Therefore, managed C++ uses automatic memory management, as do all .NET languages.

Using the .NET mechanism, you still create objects dynamically using the new operator, but the system is responsible for deleting objects, not you. The system keeps track of references to objects, and when an object is no longer being referenced by anyone, it becomes a candidate for garbage collection. Automatic memory management has more consequences for the programmer than you might at first think:

  • Objects are always created using new. Unmanaged code lets you create objects on the heap or dynamically on the stack (as automatic variables). For garbage collection to work, objects have to be accessed through some sort of reference, which is a pointer in C++.

  • You always access class members using pointers.

  • You can’t tell when an object is going to be garbage collected.

You can still use the delete operator to destroy an object manually because that’s an inherent part of the way C++ works, but you typically leave it up to the garbage collector to handle deallocation.

start sidebar
.NET Garbage Collection

The garbage collection mechanism in .NET Framework is very sophisticated, but you don’t need to know much about it to use managed C++. In fact, it’s designed to work fine without any intervention from you at all. However, if you’re interested to know a little more about what’s going on, read on.

Memory for objects is allocated from the managed heap, the chunk of memory that the process uses to store dynamically allocated objects. Every allocation takes some space from the heap, and it’s possible that at some point heap memory will be exhausted. In theory, if this happens, the garbage collector will be invoked to see if there are any unreferenced objects whose memory can be reclaimed to minimize the size of the heap.

In reality, it’s not quite that simple. Every dynamically created .NET Framework object belongs to a generation. Objects are placed in generation 0 when they are created, with higher-numbered generations being filled with longer lived objects. Dividing objects into generations means that you don’t have to run the garbage collector on all the objects in the heap, and you need to consider only the age of a particular object.

Garbage collection occurs when generation 0 is full. Dead objects are reclaimed, any objects that survived the collection are promoted to generation 1, and new objects are added to generation 0 again. The garbage collector improves its efficiency by always running a collection on generation 0 first. If that doesn’t free up enough memory, the garbage collector can move on to run a collection on the next generation. At present, only three generations (0, 1, and 2) are supported by .NET.

You usually let the garbage collector decide when to perform a collection, but you can use the Collect static method of the System::GC class to force a collection if you know you’ll have a lot of reclaimable objects in your code. Collect lets you run a default collection or specify a particular generation. If you’re interested in finding out what generation a particular object belongs to, you can use the System::GC::GetGeneration method, passing in an object reference.

end sidebar

Finalizers

All .NET reference types (__gc classes in managed C++) inherit a method named Finalize from the ultimate base class, System::Object. Finalize allows an object to free up resources and perform other clean-up operations before the object is garbage collected. Any managed class can override Finalize if it needs to perform these tasks.

It’s important to note that Finalize isn’t the same as a traditional C++ destructor. Destructors are deterministic, meaning you can always tell when the destructor for an object is going to be called. It might be hard to decide when a destructor will be called in complex code with a lot of pointers and references being passed around, but it’s always possible because the destructor is invoked explicitly at some point.

Finalizers, on the other hand, are non-deterministic. You can’t tell when the Finalize method for an object will be called because that depends on when the object is garbage collected. And although you can force a garbage collection, it’s typically up to the system to decide when to do it. This means that you shouldn’t put any code into Finalize that you rely on being called at a particular point in the program because you can’t guarantee that it will happen.

So what do you use Finalize for? You use it to release any unmanaged resources that your object holds, such as file handles, window handles, or database connections. You don’t need to implement Finalize to deal with managed resources because the garbage collector will deal with them.

Although finalizers are not the same as destructors, in managed C++, you use the normal C++ syntax to write a destructor, and the compiler then writes you a .NET Finalize method. So, a destructor such as this

~MyClass() { Console::WriteLine(S"Finalizing..."); }

expands to

MyClass::Finalize() { Console::WriteLine(S"Finalizing..."); MyBaseClass::Finalize(); } virtual ~MyClass() { System::GC::SuppressFinalize(this); MyClass::Finalize(); }

The destructor calls the Finalize method for the class, which contains any code you supplied in the destructor. Finalize then calls the finalizer for its base class, if any. The call to SuppressFinalize prevents the garbage collector from undertaking any other finalization action. If the destructor has been called in code, the object is explicitly deallocated and you don’t want the garbage collector trying to do any finalization itself.

You have to implement finalizers using the C++ destructor syntax. You can’t implement Finalize directly for a class. If you try to do so, you’ll get a compiler error.

Note

Implement Finalize only on classes that need it because object reclamation during garbage collection takes longer when finalizers have to be called.

Implementing a Finalizer

The following short exercise shows how to implement a finalizer for a managed class.

  1. Create a new Visual C++ Console Application (.NET) project named TestFinalize.

  2. Open the TestFinalize.cpp file, and add the definition of a managed class before the _tmain function, like this:

    __gc class Tester { public: Tester() { Console::WriteLine(S"Tester constructor"); } ~Tester() { Console::WriteLine(S"Tester finalizer"); } };

    The class contains code for a constructor and a destructor, so the compiler will generate a Finalize method automatically.

  3. Create a Tester object in the _tmain function, as shown here:

    int _tmain() { Console::WriteLine(S"Finalization Test"); // Create an object Tester* pt = new Tester(); Console::WriteLine(S"End of Test"); return 0; } 
  4. Compile and run the code. You should see the messages from both functions on the console, indicating that both constructor and destructor functions have been called. Notice that the destructor message is printed after the “End of Test” message, showing that the object is being destroyed as part of program cleanup.

  5. Change the code so that the object is deleted immediately before the “End of Test” message.

    int _tmain() { Console::WriteLine(S"Finalization Test"); // Create an object Tester* pt = new Tester(); // Delete the object delete pt; Console::WriteLine(S"End of Test"); return 0; }
  6. Build and run the code. You’ll see that the message from the destructor is printed before the “End of Test” message, showing that the object has been destroyed at this point.

A Few Points About Finalize

There are three points that you need to be aware of when using finalizers in the .NET sense rather than the C++ sense. First, objects with finalizers take longer to allocate and longer to destroy because the finalizer has to be called.

Second, no guarantee is made as to what order finalizers will be called in, which can be problematic when using nested objects. Suppose that class A contains a reference to an object of class B. Both finalizers will be called when an A object is destroyed, but you can’t tell which will be called first, which can cause problems if the class B finalizer is called first and if class A references the embedded B object in its finalizer. For this reason, you shouldn’t refer to embedded objects in finalizers.

Finally, finalizers aren’t called on application shutdown for objects that are still being used, such as those being used by background threads or objects that are created as part of the finalization process. Although all system resources will be freed up when the application exits, objects that don’t have their finalizers called might not get a chance to clean up properly.

Using a Dispose Method

The .NET Framework uses Finalize for cleanup when an object is garbage collected, and it’s called through the destructor when C++ objects are destroyed.

Most other .NET languages—especially C# and Microsoft Visual Basic—don’t give you any control over object lifetime. These languages offer no equivalent of the C++ destructor, so Finalize will be called only when the garbage collector reclaims the object.

What if you want to make sure that an object releases the resources it holds? Putting the code in Finalize is a bad idea because you don’t know when it will be called. In the past, programmers had to code improvised methods in their classes that clients had to call to force object cleanup. Having all programmers invent their own version of the same mechanism isn’t very efficient, so Microsoft formalized the process and introduced the IDisposable interface in the first release of the .NET Framework.

Note

Interfaces are related to inheritance and are covered in detail in Chapter 8. This section will show you how to implement a Dispose method for your classes, but see Chapter 8 for details of how interfaces work if you haven’t already met them.

Classes that implement IDisposable have to implement a Dispose method to free unmanaged resources. Clients can explicitly call Dispose when they have finished with the object to free up the resources it used and leave the runtime to garbage collect the object at an appropriate point.

You might want to implement IDisposable on your classes if

  • A class uses unmanaged resources that the client might want to release at a defined point in the code.

  • You want to use a managed C++ class from other .NET languages.

The following exercise shows how to add Dispose support to a class.

  1. Continue with the project from the previous exercise.

  2. Edit the class definition so that the class inherits from IDisposable, as shown here:

    __gc class Tester : public IDisposable

    If you haven’t met inheritance before, this syntax will be explained in Chapter 8.

  3. Inheriting from IDisposable means that a class must implement a Dispose method, so add a new public method to the class.

    void Dispose() { Console::WriteLine(S"Tester Dispose"); }

    The function has to be called Dispose, have no arguments, and have a void return type.

  4. Arrange to call Dispose in the _tmain function.

    int _tmain(void) { Console::WriteLine(S"Finalization Test"); // Create an object Tester* pt = new Tester(); // Call Dispose pt->Dispose(); Console::WriteLine(S"End of Test"); return 0; }
  5. Build and run the code. You’ll see that the Dispose method is called before the object is destroyed.

Integrating Finalize and Dispose

There’s potential for serious problems when integrating Finalize and Dispose. The Dispose method releases resources on demand, but the object is still alive, which means that someone could try to use the object once it has released its resources, and we don’t want that to happen.

Another problem is that Dispose releases resources on demand, whereas Finalize releases them when the object is garbage collected, which means that Finalize has to know whether resources have already been released by a call to Dispose so that it doesn’t try to release them a second time.

The solution is to add a flag to the class that records whether the object’s resources have been released. You can check this flag whenever the object is accessed and throw an exception if the object is accessed after its resources have been released. The following exercise shows you how to do this.

  1. Continue with the existing project, and add a private Boolean member to the class.

    __gc class Tester : public IDisposable { bool bDisposed; ... }
  2. Set the flag to false in the constructor.

    Tester() { Console::WriteLine(S"Tester constructor"); bDisposed = false; }
  3. Set the flag to true in the Dispose method to show that resources have been released.

    void Dispose() { Console::WriteLine(S"Tester Dispose"); // Release any resources bDisposed = true; }
  4. Check the flag in the destructor, and use it to control whether resources are released.

    ~Tester() { Console::WriteLine(S"Tester finalizer"); if (bDisposed == false) { Console::WriteLine(S"Releasing resources..."); } }
  5. To show how you would protect against calling a method on an object that has released its resources, add a public method to the class:

    void aMethod() { if (bDisposed) throw new ObjectDisposedException(S"Tester"); Console::WriteLine(S"Tester aMethod"); }

    The method checks whether the flag has been set to true. If it has, the code throws an ObjectDisposedException, one of the standard exceptions provided in the System namespace to handle such an occurrence.

  6. Add a call to aMethod immediately after the call to Dispose in the code.

    int _tmain(void) { Console::WriteLine(S"Finalization Test"); // Create an object Tester* pt = new Tester(); // Call Dispose pt->Dispose(); // Try calling a method on the object... pt->aMethod(); Console::WriteLine(S"End of Test"); return 0; }
  7. Build and run the code. Calling the method will result in the program failing as the exception is generated, but it does prevent the caller from trying to use an object that has released its resources.




Microsoft Visual C++  .NET(c) Step by Step
Microsoft Visual C++ .NET(c) Step by Step
ISBN: 735615675
EAN: N/A
Year: 2003
Pages: 208

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net