Практическое руководство. Определение и использование классов и структур (C++/CLI)

В этой статье показано, как определить и использовать определяемые пользователем ссылочные типы и типы значений в C++/CLI.

Создание экземпляра объекта

Типы ссылок (ссылок) можно создать только в управляемой куче, а не в стеке или в собственной куче. Типы значений можно создать в стеке или управляемой куче.

// mcppv2_ref_class2.cpp
// compile with: /clr
ref class MyClass {
public:
   int i;

   // nested class
   ref class MyClass2 {
   public:
      int i;
   };

   // nested interface
   interface struct MyInterface {
      void f();
   };
};

ref class MyClass2 : public MyClass::MyInterface {
public:
   virtual void f() {
      System::Console::WriteLine("test");
   }
};

public value struct MyStruct {
   void f() {
      System::Console::WriteLine("test");
   }
};

int main() {
   // instantiate ref type on garbage-collected heap
   MyClass ^ p_MyClass = gcnew MyClass;
   p_MyClass -> i = 4;

   // instantiate value type on garbage-collected heap
   MyStruct ^ p_MyStruct = gcnew MyStruct;
   p_MyStruct -> f();

   // instantiate value type on the stack
   MyStruct p_MyStruct2;
   p_MyStruct2.f();

   // instantiate nested ref type on garbage-collected heap
   MyClass::MyClass2 ^ p_MyClass2 = gcnew MyClass::MyClass2;
   p_MyClass2 -> i = 5;
}

Неявно абстрактные классы

Неявно абстрактный класс нельзя создать экземпляр. Класс неявно абстрагируется, когда:

  • базовый тип класса — это интерфейс и
  • Класс не реализует все функции-члены интерфейса.

Возможно, не удается создать объекты из класса, производного от интерфейса. Причина может быть в том, что класс неявно абстрактен. Дополнительные сведения об абстрактных классах см . в абстрактных.

В следующем примере кода показано, что MyClass класс нельзя создать экземпляр, так как функция MyClass::func2 не реализована. Чтобы включить компиляцию примера, раскомментируйте MyClass::func2его.

// mcppv2_ref_class5.cpp
// compile with: /clr
interface struct MyInterface {
   void func1();
   void func2();
};

ref class MyClass : public MyInterface {
public:
   void func1(){}
   // void func2(){}
};

int main() {
   MyClass ^ h_MyClass = gcnew MyClass;   // C2259
                                          // To resolve, uncomment MyClass::func2.
}

Видимость типов

Вы можете управлять видимостью типов среды CLR. При ссылке на сборку вы определяете, видны ли типы в сборке или не отображаются за пределами сборки.

public указывает, что тип отображается для любого исходного файла, содержащего #using директиву для сборки, содержащей тип. private указывает, что тип не отображается в исходных файлах, содержащих #using директиву для сборки, содержащей тип. Однако частные типы отображаются в одной сборке. По умолчанию видимость для класса private.

По умолчанию до Visual Studio 2005 собственные типы имели общедоступную доступность за пределами сборки. Включите предупреждение компилятора (уровень 1) C4692 , чтобы узнать, где частные собственные типы используются неправильно. Используйте make_public pragma для предоставления общедоступной доступности собственному типу в файле исходного кода, который нельзя изменить.

Дополнительные сведения см. в разделе Директива using.

В следующем примере показано, как объявить типы и указать их специальные возможности, а затем получить доступ к этим типам внутри сборки. Если на сборку с частными типами ссылается использование, #usingотображаются только общедоступные типы в сборке.

// type_visibility.cpp
// compile with: /clr
using namespace System;
// public type, visible inside and outside assembly
public ref struct Public_Class {
   void Test(){Console::WriteLine("in Public_Class");}
};

// private type, visible inside but not outside assembly
private ref struct Private_Class {
   void Test(){Console::WriteLine("in Private_Class");}
};

// default accessibility is private
ref class Private_Class_2 {
public:
   void Test(){Console::WriteLine("in Private_Class_2");}
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   a->Test();

   Private_Class ^ b = gcnew Private_Class;
   b->Test();

   Private_Class_2 ^ c = gcnew Private_Class_2;
   c->Test();
}

Выходные данные

in Public_Class
in Private_Class
in Private_Class_2

Теперь давайте перезаписаем предыдущий пример, чтобы он был создан в виде библиотеки DLL.

// type_visibility_2.cpp
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref struct Public_Class {
   void Test(){Console::WriteLine("in Public_Class");}
};

// private type, visible inside but not outside the assembly
private ref struct Private_Class {
   void Test(){Console::WriteLine("in Private_Class");}
};

// by default, accessibility is private
ref class Private_Class_2 {
public:
   void Test(){Console::WriteLine("in Private_Class_2");}
};

В следующем примере показано, как получить доступ к типам за пределами сборки. В этом примере клиент использует компонент, встроенный в предыдущий пример.

// type_visibility_3.cpp
// compile with: /clr
#using "type_visibility_2.dll"
int main() {
   Public_Class ^ a = gcnew Public_Class;
   a->Test();

   // private types not accessible outside the assembly
   // Private_Class ^ b = gcnew Private_Class;
   // Private_Class_2 ^ c = gcnew Private_Class_2;
}

Выходные данные

in Public_Class

Видимость элемента

Вы можете сделать доступ к члену общедоступного класса из той же сборки, отличной от доступа к ней извне сборки с помощью пар описателей publicдоступа, protectedи private

В этой таблице приведены сведения о влиянии различных описателей доступа:

класса хранения Действие
public Член доступен внутри и за пределами сборки. Дополнительные сведения см. в разделе public.
private Член недоступен как внутри, так и за пределами сборки. Дополнительные сведения см. в разделе private.
protected Член доступен внутри и за пределами сборки, но только для производных типов. Дополнительные сведения см. в разделе protected.
internal Член является общедоступным внутри сборки, но закрытым за пределами сборки. internal — контекстно-зависимое ключевое слово. Дополнительные сведения см. в статье Context-Sensitive Keywords (C++/CLI and C++/CX) (Контекстно-зависимые ключевые слова (C++/CLI и C++/CX)).
public protected -или- protected public Член является общедоступным внутри сборки, но защищен за пределами сборки.
private protected -или- protected private Член защищен внутри сборки, но закрытый вне сборки.

В следующем примере показан открытый тип с элементами, объявленными с помощью разных описателей доступа. Затем он показывает доступ к этим членам из сборки.

// compile with: /clr
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
   void Public_Function(){System::Console::WriteLine("in Public_Function");}

private:
   void Private_Function(){System::Console::WriteLine("in Private_Function");}

protected:
   void Protected_Function(){System::Console::WriteLine("in Protected_Function");}

internal:
   void Internal_Function(){System::Console::WriteLine("in Internal_Function");}

protected public:
   void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}

public protected:
   void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}

private protected:
   void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}

protected private:
   void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};

// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Private_Function();
      Private_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   MyClass ^ b = gcnew MyClass;
   a->Public_Function();
   a->Protected_Public_Function();
   a->Public_Protected_Function();

   // accessible inside but not outside the assembly
   a->Internal_Function();

   // call protected functions
   b->Test();

   // not accessible inside or outside the assembly
   // a->Private_Function();
}

Выходные данные

in Public_Function
in Protected_Public_Function
in Public_Protected_Function
in Internal_Function
=======================
in function of derived class
in Protected_Function
in Protected_Private_Function
in Private_Protected_Function
exiting function of derived class
=======================

Теперь давайте создадим предыдущий пример в виде библиотеки DLL.

// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
   void Public_Function(){System::Console::WriteLine("in Public_Function");}

private:
   void Private_Function(){System::Console::WriteLine("in Private_Function");}

protected:
   void Protected_Function(){System::Console::WriteLine("in Protected_Function");}

internal:
   void Internal_Function(){System::Console::WriteLine("in Internal_Function");}

protected public:
   void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}

public protected:
   void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}

private protected:
   void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}

protected private:
   void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};

// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Private_Function();
      Private_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

В следующем примере используется компонент, созданный в предыдущем примере. В нем показано, как получить доступ к членам извне сборки.

// compile with: /clr
#using "type_member_visibility_2.dll"
using namespace System;
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Public_Function();
      Public_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   MyClass ^ b = gcnew MyClass;
   a->Public_Function();

   // call protected functions
   b->Test();

   // can't be called outside the assembly
   // a->Private_Function();
   // a->Internal_Function();
   // a->Protected_Private_Function();
   // a->Private_Protected_Function();
}

Выходные данные

in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================

Общедоступные и частные собственные классы

Собственный тип можно ссылаться на управляемый тип. Например, функция в управляемом типе может принимать параметр, тип которого является собственной структурой. Если управляемый тип и функция являются общедоступными в сборке, то собственный тип также должен быть общедоступным.

// native type
public struct N {
   N(){}
   int i;
};

Затем создайте файл исходного кода, который использует собственный тип:

// compile with: /clr /LD
#include "mcppv2_ref_class3.h"
// public managed type
public ref struct R {
   // public function that takes a native type
   void f(N nn) {}
};

Теперь скомпилируйте клиент:

// compile with: /clr
#using "mcppv2_ref_class3.dll"

#include "mcppv2_ref_class3.h"

int main() {
   R ^r = gcnew R;
   N n;
   r->f(n);
}

Статические конструкторы

Тип СРЕДЫ CLR, например класс или структуру, может иметь статический конструктор, который можно использовать для инициализации статических элементов данных. Статический конструктор вызывается по крайней мере один раз и вызывается перед первым доступом к любому статическому элементу типа.

Конструктор экземпляра всегда выполняется после статического конструктора.

Компилятор не может встроить вызов конструктора, если класс имеет статический конструктор. Компилятор не может встроить вызов какой-либо функции-член, если класс является типом значения, имеет статический конструктор и не имеет конструктора экземпляра. ClR может встраить вызов, но компилятор не может.

Определите статический конструктор как частную функцию-член, так как она должна вызываться только средой CLR.

Дополнительные сведения о статических конструкторах см. в статье "Определение статического конструктора интерфейса" (C++/CLI).

// compile with: /clr
using namespace System;

ref class MyClass {
private:
   static int i = 0;

   static MyClass() {
      Console::WriteLine("in static constructor");
      i = 9;
   }

public:
   static void Test() {
      i++;
      Console::WriteLine(i);
   }
};

int main() {
   MyClass::Test();
   MyClass::Test();
}

Выходные данные

in static constructor
10
11

Семантика указателя this

При использовании C++\CLI для определения типов this указатель в ссылочного типа имеет дескриптор типа. Указатель this в типе значения имеет внутренний указатель типа.

Эти различные семантики this указателя могут вызвать непредвиденное поведение при вызове индексатора по умолчанию. В следующем примере показан правильный способ доступа к индексатору по умолчанию как в типе ссылок, так и в типе значения.

Дополнительные сведения см. в разделе "Обработка оператора объектов" (^) и interior_ptr (C++/CLI)

// compile with: /clr
using namespace System;

ref struct A {
   property Double default[Double] {
      Double get(Double data) {
         return data*data;
      }
   }

   A() {
      // accessing default indexer
      Console::WriteLine("{0}", this[3.3]);
   }
};

value struct B {
   property Double default[Double] {
      Double get(Double data) {
         return data*data;
      }
   }
   void Test() {
      // accessing default indexer
      Console::WriteLine("{0}", this->default[3.3]);
   }
};

int main() {
   A ^ mya = gcnew A();
   B ^ myb = gcnew B();
   myb->Test();
}

Выходные данные

10.89
10.89

Функции скрытия по сигнатуре

В стандартном C++функция в базовом классе скрывается функцией, которая имеет то же имя в производном классе, даже если функция производного класса не имеет того же типа или количества параметров. Она называется семантикой скрытия по имени . В ссылочных типах функция в базовом классе скрывается только функцией в производном классе, если имя и список параметров совпадают. Она называется семантикой скрытия по сигнатуре .

Класс считается классом скрытия по подписи, когда все его функции помечены в метаданных как hidebysig. По умолчанию все классы, созданные под управлением /clr , имеют hidebysig функции. Если класс имеет hidebysig функции, компилятор не скрывает функции по имени в каких-либо прямых базовых классах, но если компилятор сталкивается с классом скрытия по имени в цепочке наследования, он продолжает это поведение скрытия по имени.

При семантике скрытия по сигнатуре при вызове функции компилятор определяет наиболее производный класс, содержащий функцию, которая может удовлетворить вызов функции. Если в классе есть только одна функция, которая удовлетворяет вызову, компилятор вызывает такую функцию. Если в классе существует несколько функций, которые могут удовлетворить вызов, компилятор использует правила разрешения перегрузки, чтобы определить, какую функцию следует вызывать. Дополнительные сведения о правилах перегрузки см. в разделе "Перегрузка функций".

Для данного вызова функции функция в базовом классе может иметь сигнатуру, которая делает ее немного лучше, чем функция в производном классе. Однако если функция была явно вызвана для объекта производного класса, вызывается функция в производном классе.

Так как возвращаемое значение не считается частью сигнатуры функции, функция базового класса скрывается, если она имеет то же имя и принимает то же число аргументов, что и функция производного класса, даже если она отличается от типа возвращаемого значения.

В следующем примере показано, что функция в базовом классе не скрыта функцией в производном классе.

// compile with: /clr
using namespace System;
ref struct Base {
   void Test() {
      Console::WriteLine("Base::Test");
   }
};

ref struct Derived : public Base {
   void Test(int i) {
      Console::WriteLine("Derived::Test");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   // Test() in the base class will not be hidden
   t->Test();
}

Выходные данные

Base::Test

В следующем примере показано, что компилятор Microsoft C++ вызывает функцию в самом производном классе, даже если преобразование требуется для сопоставления одного или нескольких параметров, а не вызывает функцию в базовом классе, которая лучше подходит для вызова функции.

// compile with: /clr
using namespace System;
ref struct Base {
   void Test2(Single d) {
      Console::WriteLine("Base::Test2");
   }
};

ref struct Derived : public Base {
   void Test2(Double f) {
      Console::WriteLine("Derived::Test2");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   // Base::Test2 is a better match, but the compiler
   // calls a function in the derived class if possible
   t->Test2(3.14f);
}

Выходные данные

Derived::Test2

В следующем примере показано, что можно скрыть функцию, даже если базовый класс имеет ту же сигнатуру, что и производный класс.

// compile with: /clr
using namespace System;
ref struct Base {
   int Test4() {
      Console::WriteLine("Base::Test4");
      return 9;
   }
};

ref struct Derived : public Base {
   char Test4() {
      Console::WriteLine("Derived::Test4");
      return 'a';
   }
};

int main() {
   Derived ^ t = gcnew Derived;

   // Base::Test4 is hidden
   int i = t->Test4();
   Console::WriteLine(i);
}

Выходные данные

Derived::Test4
97

Конструкторы копии

Стандарт C++ говорит, что конструктор копирования вызывается при перемещении объекта, таким образом, что объект создается и уничтожается в том же адресе.

Однако если функция, скомпилированная в MSIL, вызывает собственную функцию, в которой собственный класс (или несколько) передается по значению и где собственный класс имеет конструктор копирования или деструктор, конструктор копирования не вызывается, а объект уничтожается по другому адресу, чем при создании. Это поведение может вызвать проблемы, если класс имеет указатель на себя или если код отслеживает объекты по адресу.

Дополнительные сведения см. в разделе /clr (компиляция CLR).

В следующем примере показано, когда конструктор копирования не создается.

// compile with: /clr
#include<stdio.h>

struct S {
   int i;
   static int n;

   S() : i(n++) {
      printf_s("S object %d being constructed, this=%p\n", i, this);
   }

   S(S const& rhs) : i(n++) {
      printf_s("S object %d being copy constructed from S object "
               "%d, this=%p\n", i, rhs.i, this);
   }

   ~S() {
      printf_s("S object %d being destroyed, this=%p\n", i, this);
   }
};

int S::n = 0;

#pragma managed(push,off)
void f(S s1, S s2) {
   printf_s("in function f\n");
}
#pragma managed(pop)

int main() {
   S s;
   S t;
   f(s,t);
}

Выходные данные

S object 0 being constructed, this=0018F378
S object 1 being constructed, this=0018F37C
S object 2 being copy constructed from S object 1, this=0018F380
S object 3 being copy constructed from S object 0, this=0018F384
S object 4 being copy constructed from S object 2, this=0018F2E4
S object 2 being destroyed, this=0018F380
S object 5 being copy constructed from S object 3, this=0018F2E0
S object 3 being destroyed, this=0018F384
in function f
S object 5 being destroyed, this=0018F2E0
S object 4 being destroyed, this=0018F2E4
S object 1 being destroyed, this=0018F37C
S object 0 being destroyed, this=0018F378

Деструкторы и методы завершения

Деструкторы в ссылочного типа выполняют детерминированную очистку ресурсов. Методы завершения очищают неуправляемые ресурсы и могут вызываться детерминированным деструктором или недетерминированным сборщиком мусора. Сведения о деструкторах в стандартном C++см. в разделе "Деструкторы".

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

Сборщик мусора СРЕДЫ CLR удаляет неиспользуемые управляемые объекты и освобождает память, когда они больше не требуются. Однако тип может использовать ресурсы, которые сборщик мусора не знает, как освободить. Эти ресурсы называются неуправляемыми ресурсами (например, дескрипторами собственных файлов). Рекомендуется освободить все неуправляемые ресурсы в средстве завершения. Сборщик мусора освобождает управляемые ресурсы недетерминированно, поэтому небезопасно ссылаться на управляемые ресурсы в методе завершения. Это потому, что возможно, сборщик мусора уже очистил их.

Метод завершения Visual C++ не совпадает с методом Finalize . (Документация по CLR использует метод завершения и Finalize синонимы метода). Метод Finalize вызывается сборщиком мусора, который вызывает каждый метод завершения в цепочке наследования классов. В отличие от деструкторов Visual C++ вызов метода завершения производного класса не приводит к вызову компилятора метода завершения во всех базовых классах.

Так как компилятор Microsoft C++ поддерживает детерминированный выпуск ресурсов, не пытайтесь реализовать Dispose или Finalize методы. Однако если вы знакомы с этими методами, вот как метод завершения Visual C++ и деструктор, который вызывает сопоставление средства завершения с шаблоном Dispose :

// Visual C++ code
ref class T {
   ~T() { this->!T(); }   // destructor calls finalizer
   !T() {}   // finalizer
};

// equivalent to the Dispose pattern
void Dispose(bool disposing) {
   if (disposing) {
      ~T();
   } else {
      !T();
   }
}

Управляемый тип также может использовать управляемые ресурсы, которые вы предпочитаете выпускать детерминированно. Возможно, сборщик мусора не может освободить объект недетерминированно в какой-то момент после того, как объект больше не требуется. Детерминированный выпуск ресурсов может значительно повысить производительность.

Компилятор Microsoft C++ позволяет определению деструктора деструктору детерминированно очищать объекты. Используйте деструктор, чтобы освободить все ресурсы, которые требуется детерминировать. Если средство завершения присутствует, вызовите его из деструктора, чтобы избежать дублирования кода.

// 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 resources
      // ...
   }
};

Если код, который использует тип, не вызывает деструктор, сборщик мусора в конечном итоге освобождает все управляемые ресурсы.

Наличие деструктора не означает наличие средства завершения. Однако наличие средства завершения подразумевает, что необходимо определить деструктор и вызвать метод завершения из этого деструктора. Этот вызов обеспечивает детерминированный выпуск неуправляемых ресурсов.

Вызов деструктора подавляет ( с помощью SuppressFinalize— завершение объекта). Если деструктор не вызывается, метод завершения типа в конечном итоге будет вызываться сборщиком мусора.

Вы можете повысить производительность, вызвав деструктор для детерминированного очистки ресурсов объекта, вместо того, чтобы разрешить clR недетерминированно завершить объект.

Код, написанный в Visual C++ и скомпилированный с помощью /clr деструктора типа, если:

  • Объект, созданный с помощью семантики стека, выходит из область. Дополнительные сведения см. в разделе Семантика стека C++ для ссылочных типов.

  • Исключение возникает во время построения объекта.

  • Объект является членом объекта, деструктор которого выполняется.

  • Оператор удаления вызывается дескриптором (дескриптор оператора объекта (^)).

  • Вы явно вызываете деструктор.

Если клиент, написанный на другом языке, использует тип, деструктор вызывается следующим образом:

  • При вызове Dispose.

  • При вызове Dispose(void) типа.

  • Если тип выходит из область в инструкции C#using.

Если вы не используете семантику стека для ссылочных типов и создаете объект ссылочного типа в управляемой куче, используйте синтаксис try-finally , чтобы убедиться, что исключение не препятствует запуску деструктора.

// compile with: /clr
ref struct A {
   ~A() {}
};

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

Если тип имеет деструктор, компилятор создает Dispose метод, реализующий IDisposable. Если тип, написанный в Visual C++ и имеющий деструктор, используемый из другого языка, вызов IDisposable::Dispose этого типа вызывает деструктор типа. Если тип используется из клиента Visual C++, вы не можете напрямую вызвать Dispose. Вместо этого вызов деструктора можно вызвать с помощью delete оператора.

Если тип имеет метод завершения, компилятор создает Finalize(void) метод, который переопределяет Finalize.

Если тип имеет метод завершения или деструктор, компилятор создает Dispose(bool) метод в соответствии с шаблоном конструктора. (Дополнительные сведения см. в разделе Удаление шаблона). Вы не можете явно создавать или вызывать Dispose(bool) в Visual C++.

Если тип имеет базовый класс, соответствующий шаблону конструктора, деструкторы для всех базовых классов вызываются при вызове деструктора для производного класса. (Если тип написан в Visual C++, компилятор гарантирует, что типы реализуют этот шаблон.) Другими словами, деструктор ссылочного класса цепочки к его базам и элементам, указанным стандартом C++. Сначала выполняется деструктор класса. Затем деструкторы для его членов выполняются в обратном порядке, в котором они были созданы. Наконец, деструкторы для своих базовых классов выполняются в обратном порядке, в котором они были созданы.

Деструкторы и методы завершения не допускаются внутри типов значений или интерфейсов.

Метод завершения может быть определен или объявлен только в типе ссылки. Как конструктор и деструктор, метод завершения не имеет возвращаемого типа.

После запуска метода завершения объекта также вызываются методы завершения в любых базовых классах, начиная с наименьшего производного типа. Методы завершения для элементов данных не автоматически связаны с методом завершения класса.

Если средство завершения удаляет собственный указатель в управляемом типе, необходимо убедиться, что ссылки на собственный указатель не собираются преждевременно. Вызов деструктора управляемого типа вместо использования KeepAlive.

Во время компиляции можно определить, имеет ли тип метод завершения или деструктор. Дополнительные сведения см. в статье Compiler Support for Type Traits (C++/CLI and C++/CX) (Поддержка признаков типов компилятором (C++/CLI и C++/CX)).

В следующем примере показаны два типа: один из них содержит неуправляемые ресурсы и управляемые ресурсы, которые получают детерминированные.

// 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");
}

См. также

Классы и структуры