Condividi tramite


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. (For information about destructors in standard C++, see Destructors (C++).)

class classname {
   ~classname() {}   // destructor
   !classname() {}   // finalizer
};

Parameters

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

Remarks

The behavior of destructors in a managed Visual C++ class differs from the behavior in previous releases. For more information on this change, see Changes in Destructor Semantics.

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.

// Visual 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 (nondeterministically). 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:

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

  • A call to Dispose.

  • Calling Dispose(void) on the type.

  • 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.cpp
// compile with: /clr
ref struct A {
   ~A() {}
};

int main() {
   A ^ MyA = gcnew A;
   try {
      // use MyA
   }
   finally {
      delete MyA;
   }
}

If your type has a destructor, the compiler will generate a Dispose method that implements IDisposable. If a type authored in Visual 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 Visual 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 (Implementing Finalize and Dispose to Clean Up Unmanaged Resources). 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 Visual 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. For more information, see Compiler Support for Type Traits.

Example

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

// destructors_finalizers_2.cpp
// compile with: /clr
#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 w("systest.txt");
   CRTFileWriter ^ w2 = gcnew CRTFileWriter("crttest.txt");
}

See Also

Concepts

Classes and Structs (Managed)