< Class, Object & Managed Code 7 | Main | C++ .NET Value Type 1 >


 

 

Object-Oriented Programming:

Class, Object and Managed Code 8

 

 

What we have in this page?

 

  1. Declaring an Explicit Finalize() (!R)

  2. What This Means Going from Managed Extensions for C++ to Visual C++ 2005

  3. Destructors and Finalizers in Visual C++

  4. Example

 

 

Declaring an Explicit Finalize() (!R)

 

In the new syntax, as we've seen, the destructor is synthesized into the Dispose() method. This means that in cases where the destructor is not explicitly invoked, the garbage collector, during finalization, will not as before find an associated Finalize() method for the object. In order to support both destruction and finalization, the new syntax has introduced a special syntax for providing a finalizer. For example:

 

public ref class R

{

   public:

       !R() { Console::WriteLine("I am the R::finalizer()!"); }

};

The ! prefix is meant to suggest the analogous tilde (~) that introduces a class destructor, that is, both post-lifetime methods have a token prefixing the name of the class. If the synthesized Finalize() method occurs within a derived class, an invocation of the base class Finalize() method is inserted at its end. If the destructor is explicitly invoked, the finalizer is suppressed. Here is what the transformation might look like:

// internal transformation under new syntax

public ref class R

{

    public:

        void Finalize()

        {

            Console::WriteLine("I am the R::finalizer()!");

        }

};

What This Means Going from Managed Extensions for C++ to Visual C++ 2005

 

This means that the runtime behavior of a Managed Extensions for C++ program is silently changed when compiled under Visual C++ 2005 whenever a reference class contains a non-trivial destructor. The required translation algorithm seems to be the following:

  1. If a destructor is present, rewrite that to be the class finalizer.

  2. If a Dispose() method is present, rewrite that into the class destructor.

  3. If a destructor is present but there is no Dispose() method, retain the destructor while carrying out the first item.

In moving your code from Managed Extensions to the new syntax, it is possible to miss performing this transformation. If the application depended in some way on the execution of associated finalization methods, then the behavior of the application will silently differ.

 

Destructors and Finalizers in Visual C++

 

Destructors in a reference type perform deterministic clean up of your resources. Finalizers clean up unmanaged resources and can be called deterministically by the destructor or non-deterministically by the garbage collector.

class classname

{

   ~classname() {}   // destructor

   !classname() {}   // finalizer

};

 

Parameters

classname

The name of the class for which you are creating the destructor or finalizer.

 

Table 4

 

The behavior of destructors in a managed C++ class differs from the behavior in previous releases. The common language runtime's garbage collector deletes unused managed objects and releases their memory when no longer needed. However, a type may use resources that the garbage collector does not know how to release. These resources are known as unmanaged resources (native file handles, for example). You should release all unmanaged resources in the finalizer. Because managed resources are released non-deterministically by the garbage collector, it is not safe to refer to managed resources in a finalizer because it is possible that the garbage collector has already cleaned up that managed resource.

A Visual C++ finalizer is not the same as the Finalize() method (common language runtime documentation uses finalizer and the Finalize() method synonymously). The Finalize() method is called by the garbage collector, which invokes each finalizer in a class inheritance chain. Unlike Visual C++ destructors, calling a derived class finalizer does not cause the compiler to invoke call the finalizer in all base classes. Because the Visual C++ compiler provides support for deterministic release of resources, Visual C++ programmers should not attempt to implement the Dispose() or Finalize() methods. However, if you are familiar with implementing these methods, this is how a Visual C++ finalizer and a destructor that calls the finalizer maps to the Dispose() pattern.

// C++ code

ref class T

{

      ~T() { this->!T(); }   // destructor calls finalizer

      !T() {}   // finalizer

};

 

// equivalent Dispose pattern

void Dispose(bool disposing)

{

      if (disposing)

      {

            ~T();

      }

      else

      {

            !T();

      }

}

A managed type may also use managed resources that you would prefer to release deterministically, and not leave to the garbage collector to release at some point after the object is no longer needed (non-deterministically). Deterministically releasing resources can significantly improve performance. The Visual C++ compiler lets you define a destructor to deterministically clean up objects. All resources that you want to deterministically release should be released in the destructor. If a finalizer is present, you should call your type's finalizer from the destructor, to avoid code duplication.

// destructors_finalizers_1.cpp

// compile with: /clr /c

ref struct A

{

      // destructor cleans up all resources

      ~A()

      {

            // clean up code to release managed resource

            // ...

            // to avoid code duplication

            // call finalizer to release unmanaged resources

            this->!A();

      }

      // finalizer cleans up unmanaged resources

      // destructor or garbage collector will

      // clean up managed resources

      !A()

      {

            // clean up code to release unmanaged resource

            // ...

      }

};

If the code consuming your type does not call the destructor, the garbage collector will eventually release all managed resources. The presence of a destructor does not imply the presence of a finalizer. However, a finalizer implies that you will define a destructor, and call the finalizer from the destructor. This provides for the deterministic release of unmanaged resources. Calling the destructor will suppress (with SuppressFinalize()) finalization of the object. If the destructor is not called, your type's finalizer will eventually be called by the garbage collector.

Deterministically cleaning up your object's resources by calling the destructor can increase performance compared with letting the common language runtime non-deterministically finalize the object. Code authored in Visual C++ and compiled with /clr will run a type's destructor for the following reasons:

  1. If an object created using stack semantics goes out of scope.

  2. If an exception is thrown during the object's construction.

  3. If the object is a member in an object whose destructor is running.

  4. If you call the delete operator (C++) on a handle (^ (Handle to object on Managed Heap)).

  5. If you explicitly call the destructor.

If your type is being consumed by a client authored in another language, the destructor will be called when:

  1. A call to Dispose().

  2. Calling Dispose(void) on the type.

  3. If the type goes out of scope in a C# using statement.

If you create an object of a reference type on the managed heap (not using stack semantics for reference types), use try/finally syntax (try-finally statement) to ensure that an exception does not prevent the destructor from running:

// clr destructors example

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

ref struct A

{

   ~A() {}

};

 

int main()

{

      A ^ MyA = gcnew A;

 

      try

      {

         // use MyA

      }

      finally

      {

         delete MyA;

      }

   return 0;

}

 

Output: None

If your type has a destructor, the compiler will generate a Dispose() method that implements IDisposable. If a type authored in C++ with a destructor is consumed from another language, calling IDisposable::Dispose on that type will cause the type's destructor to be called. When consumed from a C++ client, you cannot directly call Dispose(); call the destructor instead using the delete operator. If your type has a finalizer, the compiler will generate a Finalize(void) method that overrides Finalize(). If a type has either a finalizer or destructor. The compiler will generate a Dispose(bool) method, according to the design pattern. You cannot explicitly author or call Dispose(bool) in Visual C++. If a type has a base class that conforms to the design pattern, the destructors for all base classes will be called when the destructor for the derived class is called. If your type is authored in C++, the compiler will ensure that your types implement this pattern. That is, the destructor of a reference class will chain to its bases and members as specified by the C++ standard (first the classes’ destructor is run, then the destructors for its members in the reverse order they were constructed, and finally the destructors for its base classes in the reverse order they were constructed).

Destructors and finalizers are not allowed inside value types or interfaces. A finalizer can only be defined or declared in a reference type. Like a constructor and destructor, a finalizer has no return type. After an object's finalizer runs, finalizers in any base classes will also be called, beginning with the least derived type. Finalizers for data members are not automatically chained to by a class’s finalizer. If a finalizer deletes a native pointer in a managed type you will need to ensure that references to or through the native pointer are not prematurely collected; call the destructor on the managed type instead of using KeepAlive. You can detect at compile time if a type has a finalizer or destructor.

 

 

 

 

 

 

 

Program Example

 

This sample shows two types, one with unmanaged resources and one with managed resources that are deterministically released.

// destructors and finalizers

// compile with: /clr

#include "stdafx.h"

#include <vcclr.h>

#include <stdio.h>

using namespace System;

using namespace System::IO;

 

ref class SystemFileWriter

{

      FileStream ^ file;

      array<Byte> ^ arr;

      int bufLen;

      public:

            SystemFileWriter(String ^ name) : file(File::Open(name, FileMode::Append)),

                                     arr(gcnew array<Byte>(1024)) {}

            void Flush()

            {

                  file->Write(arr, 0, bufLen);

                  bufLen = 0;

            }

            ~SystemFileWriter()

            {

                  Flush();

                  delete file;

            }

};

 

ref class CRTFileWriter

{

      FILE * file;

      array<Byte> ^ arr;

      int bufLen;

      static FILE * getFile(String ^ n)

      {

            pin_ptr<const wchar_t> name = PtrToStringChars(n);

            FILE * ret = 0;

            _wfopen_s(&ret, name, L"ab");

            return ret;

      }

      public:

            CRTFileWriter(String ^ name) : file(getFile(name)), arr(gcnew array<Byte>(1024)) {}

            void Flush()

            {

                  pin_ptr<Byte> buf = &arr[0];

                  fwrite(buf, 1, bufLen, file);

                  bufLen = 0;

            }

            ~CRTFileWriter()

            {

                  this->!CRTFileWriter();

            }

            !CRTFileWriter()

            {

                  Flush();

                  fclose(file);

            }

};

 

int main()

{

      SystemFileWriter wri("systest.txt");

      CRTFileWriter ^ wri2 = gcnew CRTFileWriter("crttest.txt");

      return 0;

}

 

Output: None

 

 

 

 

 

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 | Part 8

 


 

< Class, Object & Managed Code 7 | Main | C++ .NET Value Type 1 >