Практическое руководство. Определение и использование классов и структур (C++выполняет)How to: Define and Consume Classes and Structs (C++/CLI)

В этой статье показано, как определение и использование определяемых пользователем ссылочных типов и типов значений в C++выполняет.This article shows how to define and consume user-defined reference types and value types in C++/CLI.

ОписаниеContents

При создании объектовObject instantiation

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

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

Видимость членовMember visibility

Общедоступные и частные собственных классовPublic and private native classes

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

Семантика этого указателяSemantics of the this pointer

Скрыть-функции со скрываемой сигнатуройHide-by-signature functions

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

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

При создании объектовObject instantiation

Типы ссылку (ref) могут быть созданы только в управляемой куче, не в стеке или в собственной куче.Reference (ref) types can only be instantiated on the managed heap, not on the stack or on the native heap. Типы значений могут быть созданы в стеке или управляемой кучи.Value types can be instantiated on the stack or the managed heap.

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

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

Неявно абстрактного класса не может быть создан.An implicitly abstract class can't be instantiated. Класс является абстрактным неявно, если базовый тип класса является интерфейсом, а класс не реализует все функции-члены этого интерфейса.A class is implicitly abstract if the base type of the class is an interface and the class does not implement all of the interface's member functions.

Если вы не сможете создать объекты из класса, который является производным от интерфейса, причина может быть, что данный класс является абстрактным неявно.If you are unable to construct objects from a class that's derived from an interface, the reason might be that the class is implicitly abstract. Дополнительные сведения об абстрактных классах см. в разделе абстрактный.For more information about abstract classes, see abstract.

В следующем примере кода показано, что MyClass класс не может быть создан, так как функция MyClass::func2 не реализован.The following code example demonstrates that the MyClass class cannot be instantiated because function MyClass::func2 is not implemented. Чтобы включить в примере для компиляции, раскомментируйте MyClass::func2.To enable the example to compile, uncomment 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.
}

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

Можно управлять видимостью типов среды выполнения (CLR), поэтому, если ссылка на сборку задана типы в сборке может быть видимым или невидимым за пределами сборки.You can control the visibility of common language runtime (CLR) types so that, if an assembly is referenced, types in the assembly can be visible or not visible outside the assembly.

public Указывает, что тип является видимым для любого исходного файла, содержащего #using директив для сборки, содержащей тип.public indicates that a type is visible to any source file that contains a #using directive for the assembly that contains the type. private Указывает, что тип не является видимым для исходных файлов, содержащих #using директив для сборки, содержащей тип.private indicates that a type is not visible to source files that contain a #using directive for the assembly that contains the type. Тем не менее закрытые типы, видимые в пределах той же сборки.However, private types are visible within the same assembly. По умолчанию — видимость для класса private.By default, the visibility for a class is private.

По умолчанию до Visual Studio 2005 собственные типы были открытый доступ за пределами сборки.By default prior to Visual Studio 2005, native types had public accessibility outside the assembly. Включить Предупреждение компилятора (уровень 1) C4692 поможет увидеть, где частный собственных типов используются неправильно.Enable Compiler Warning (level 1) C4692 to help you see where private native types are used incorrectly. Используйте make_public директиву pragma, чтобы предоставить открытый доступ к собственный тип в файл исходного кода, которые нельзя изменить.Use the make_public pragma to give public accessibility to a native type in a source code file that you can't modify.

Дополнительные сведения см. в разделе Директива using.For more information, see #using Directive.

Следующий пример показано, как объявлять типы и указать их доступность, а также получить доступ к эти типы в сборке.The following sample shows how to declare types and specify their accessibility, and then access those types inside the assembly. Конечно, если сборка, закрытых типов указывается с помощью #usingтолько открытые типы в сборке являются видимыми.Of course, if an assembly that has private types is referenced by using #using, only public types in the assembly are visible.

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

ВыводOutput

in Public_Class
in Private_Class
in Private_Class_2

Теперь давайте перепишем предыдущего примера, чтобы она создается как библиотеку DLL.Now, let's rewrite the previous sample so that it is built as a 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");}
};

В приведенном ниже примере показано, как для доступа к типам за пределы данной сборки.The next sample shows how to access types outside the assembly. В этом примере клиент использует компонент, который встроен в предыдущем примере.In this sample, the client consumes the component that's built in the previous sample.

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

ВыводOutput

in Public_Class

Видимость членовMember visibility

Вы может упростить доступ к членом открытого класса из той же сборки отличается от доступа к нему из за пределов сборки с помощью пары описатели доступа public, protected, и privateYou can make access to a member of a public class from within the same assembly different than access to it from outside the assembly by using pairs of the access specifiers public, protected, and private

В следующей таблице перечислены последствия различных описатели доступа:This table summarizes the effect of the various access specifiers:

ОписательSpecifier ДействиеEffect
publicpublic Элемент доступен внутри и вне сборки.Member is accessible inside and outside the assembly. См. в разделе открытый Дополнительные сведения.See public for more information.
privateprivate Член недоступен, ни внутри, ни за пределы данной сборки.Member is not accessible, neither inside nor outside the assembly. См. в разделе частного Дополнительные сведения.See private for more information.
protectedprotected Элемент доступен внутри и вне сборки, но только для производных типов.Member is accessible inside and outside the assembly, but only to derived types. См. в разделе защищенные Дополнительные сведения.See protected for more information.
internalinternal Член является общедоступной внутри сборки, но закрытый за пределы данной сборки.Member is public inside the assembly but private outside the assembly. internal — контекстно-зависимое ключевое слово.internal is a context-sensitive keyword. Дополнительные сведения см. в разделе контекстные ключевые слова.For more information, see Context-Sensitive Keywords.
открытый защищенный - или - защищенных publicpublic protected -or- protected public Член является общедоступной внутри сборки, однако защищены за пределы данной сборки.Member is public inside the assembly but protected outside the assembly.
закрытый защищенный - или - защищенных privateprivate protected -or- protected private Член является защищенным внутри сборки, но закрытый за пределы данной сборки.Member is protected inside the assembly but private outside the assembly.

В следующем примере показан открытый тип, который содержит члены, которые были определены в разные уровни доступности и затем показывает доступ к объекту этих элементов из внутри сборки.The following sample shows a public type that has members that are declared with the different accessibilities, and then shows the accessing of those members from inside the assembly.

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

ВыводOutput

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.Now let's build the previous sample as a 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("=======================");
   }
};

В следующем примере используется компонент, который создается в предыдущем примере и тем самым показано, как получить доступ к членам за пределами сборки.The following sample consumes the component that's created in the previous sample, and thereby shows how to access the members from outside the assembly.

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

ВыводOutput

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

Общедоступные и частные собственных классовPublic and private native classes

Собственный тип может ссылаться на управляемый тип.A native type can be referenced from a managed type. Например функция в управляемом типе может принимать параметр с типом собственной структуры.For example, a function in a managed type can take a parameter whose type is a native struct. Если в сборке открыты управляемый тип и функцию, затем собственный тип должны также быть открытыми.If the managed type and function are public in an assembly, then the native type must also be public.

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

Создайте файл исходного кода, который использует собственный тип:Next, create the source code file that consumes the native type:

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

Теперь скомпилируйте клиента:Now, compile a client:

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

#include "mcppv2_ref_class3.h"

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

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

Тип CLR, например, класс или структура — может иметь статический конструктор, который может использоваться для инициализации статических элементов данных.A CLR type—for example, a class or struct—can have a static constructor that can be used to initialize static data members. Статический конструктор вызывается не более одного раза и вызывается перед любой статический член типа осуществляется в первый раз.A static constructor is called at most once, and is called before any static member of the type is accessed the first time.

Конструктор экземпляра всегда выполняется после статический конструктор.An instance constructor always runs after a static constructor.

Компилятор не может выполнить подстановку вызов конструктора, если класс имеет статический конструктор.The compiler cannot inline a call to a constructor if the class has a static constructor. Компилятор не может выполнить подстановку вызов любой функции-члена, если данный класс является типом значения, имеет статический конструктор и не имеет конструктора экземпляра.The compiler cannot inline a call to any member function if the class is a value type, has a static constructor, and does not have an instance constructor. Среда CLR может вводить вызов, но компилятор не может.The CLR may inline the call, but the compiler cannot.

Определите статический конструктор как закрытая функция-член, так как он должен вызываться только средой CLR.Define a static constructor as a private member function, because it is meant to be called only by the CLR.

Дополнительные сведения о статических конструкторов, см. в разделе как: Определение статического конструктора интерфейса (C++выполняет) .For more information about static constructors, see How to: Define an Interface Static Constructor (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();
}

ВыводOutput

in static constructor
10
11

Семантика этого указателяSemantics of the this pointer

При использовании Visual C++ для определения типов, this указатель в ссылочный тип имеет тип «маркер».When you are using Visual C++ to define types, the this pointer in a reference type is of type "handle". this Указатель на тип значения имеет тип «внутренний указатель».The this pointer in a value type is of type "interior pointer".

Эти разные семантики this указатель может привести к непредвиденному поведению при вызове индексатор по умолчанию.These different semantics of the this pointer can cause unexpected behavior when a default indexer is called. В следующем примере правильный способ доступа к индексатору по умолчанию в ссылочного типа и типа значения.The next example shows the correct way to access a default indexer in both a ref type and a value type.

Дополнительные сведения см. в разделе .For more information, see

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

ВыводOutput

10.89
10.89

Скрыть-функции со скрываемой сигнатуройHide-by-signature functions

В стандартном языке C++ функцию в базовом классе скрыт функцию, которая имеет то же имя в производном классе, даже если функция производного класса не имеют одинаковый номер или тип параметров.In standard C++, a function in a base class is hidden by a function that has the same name in a derived class, even if the derived-class function does not have the same number or kind of parameters. Это называется скрыть по имени семантику.This is referred to as hide-by-name semantics. В ссылочный тип функцию в базовом классе можно только скрыть, функция в производном классе, если имя и список параметров одинаковы.In a reference type, a function in a base class can only be hidden by a function in a derived class if both the name and the parameter list are the same. Этот процесс называется по подписи семантику.This is known as hide-by-signature semantics.

Класс считается класс по подписи, когда все его функции отмечены в метаданных как hidebysig.A class is considered a hide-by-signature class when all of its functions are marked in the metadata as hidebysig. По умолчанию, все классы, созданные в /CLR имеют hidebysig функции.By default, all classes that are created under /clr have hidebysig functions. Если класс имеет hidebysig функции, компилятор не скрывает функции по имени в прямых базовых классов, но если компилятор обнаруживает скрыть по имени класса в цепочке наследования, он продолжает это поведение скрытия по имени.When a class has hidebysig functions, the compiler doesn't hide functions by name in any direct base classes, but if the compiler encounters a hide-by-name class in an inheritance chain, it continues that hide-by-name behavior.

В семантике по подписи при вызове функции для объекта, компилятор определяет наиболее производного класса, содержащего функцию, которая может удовлетворить вызова функции.Under hide-by-signature semantics, when a function is called on an object, the compiler identifies the most derived class that contains a function that could satisfy the function call. Если имеется только одна функция в классе, который могут удовлетворять вызов, компилятор вызывает эту функцию.If there is only one function in the class that could satisfy the call, the compiler calls that function. Если имеется более одной функции в классе, который могут удовлетворять вызов, компилятор использует перегрузку правила разрешения, чтобы определить, какую функцию вызвать.If there is more than one function in the class that could satisfy the call, the compiler uses overload resolution rules to determine which function to call. Дополнительные сведения о правилах перегрузки, см. в разделе перегрузка функций.For more information about overload rules, see Function Overloading.

Для вызова данной функции функции в базовом классе может иметь сигнатуру, которая становится немного лучше, чем функция в производном классе.For a given function call, a function in a base class might have a signature that makes it a slightly better match than a function in a derived class. Тем не менее если функция явно был вызван для объекта производного класса, вызывается функция в производном классе.However, if the function was explicitly called on an object of the derived class, the function in the derived class is called.

Так как возвращаемое значение не считается частью сигнатуры функции, функции базового класса является скрытым, если он имеет то же имя и принимает то же количество и тип аргументов, как функция производного класса, даже если он отличается в типе возвращаемого значения.Because the return value is not considered part of a function's signature, a base-class function is hidden if it has the same name and takes the same number and kind of arguments as a derived-class function, even if it differs in the type of the return value.

В следующем примере показано, что функция в базовом классе не скрыт с помощью функции в производном классе.The following sample shows that a function in a base class is not hidden by a function in a derived class.

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

ВыводOutput

Base::Test

В приведенном ниже примере показано, что Microsoft C++ компилятор вызывает функцию в наиболее производный класс — даже если преобразование должен соответствовать один или несколько параметров и не вызывать функцию в базовом классе, который лучше подходит для вызова функции.The next sample shows that the Microsoft C++ compiler calls a function in the most derived class—even if a conversion is required to match one or more of the parameters—and not call a function in a base class that is a better match for the function call.

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

ВыводOutput

Derived::Test2

Следующий пример показывает, что это можно скрыть функции, даже если базовый класс имеет ту же сигнатуру, как и производный класс.The following sample shows that it's possible to hide a function even if the base class has the same signature as the derived class.

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

ВыводOutput

Derived::Test4
97

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

Стандарт C++ указывает, что конструктор копии вызывается при перемещении объекта таким образом, что объект создается и уничтожается по тому же адресу.The C++ standard says that a copy constructor is called when an object is moved, such that an object is created and destroyed at the same address.

Тем не менее, если /CLR используется для компиляции и функцию, которая компилируется в собственную функцию там, где собственного класса вызовы MSIL — или более одного — передается по значению и где собственный класс имеет конструктор копии и/или деструктора, копия конструктор вызывается и уничтожения объекта адресу, отличному от которой он был создан.However, when /clr is used to compile and a function that's compiled to MSIL calls a native function where a native class—or more than one—is passed by value and where the native class has a copy constructor and/or destructor, no copy constructor is called and the object is destroyed at a different address than where it was created. Это может вызвать проблемы, если класс имеет указатель в самого себя, или если код отслеживает объекты по адресу.This could cause problems if the class has a pointer into itself, or if the code is tracking objects by address.

Дополнительные сведения см. в разделе /clr (компиляция CLR).For more information, see /clr (Common Language Runtime Compilation).

В следующем образце показано, когда конструктор копии не создается.The following sample demonstrates when a copy constructor is not generated.

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

ВыводOutput

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

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

Деструкторы в ссылочный тип выполнения детерминированную очистку ресурсов.Destructors in a reference type perform a deterministic clean-up of resources. Методы завершения очистки неуправляемых ресурсов и могут вызываться детерминировано, деструктором или недетерминированным образом сборщиком мусора.Finalizers clean up unmanaged resources and can be called deterministically by the destructor or nondeterministically by the garbage collector. Сведения о деструкторах в стандартном языке C++, см. в разделе деструкторы.For information about destructors in standard C++, see Destructors.

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

Поведение деструкторов в управляемом классе Visual C++ отличается от управляемых расширений для C++.The behavior of destructors in a managed Visual C++ class differs from Managed Extensions for C++. Дополнительные сведения об этом изменении см. в разделе изменения в семантике деструктора.For more information about this change, see Changes in Destructor Semantics.

Сборщик мусора CLR удаляет неиспользуемые управляемые объекты и освобождает память, когда они больше не требуются.The CLR garbage collector deletes unused managed objects and releases their memory when they are no longer required. Однако тип может использовать ресурсы, которые сборщик мусора не знает, как освободить.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). Мы рекомендуем выпускать все неуправляемые ресурсы, в методе завершения.We recommend that you release all unmanaged resources in the finalizer. Так как управляемые ресурсы освобождены недетерминированным образом сборщиком мусора, небезопасно для ссылки на управляемые ресурсы в методе завершения так, как это возможно, сборщик мусора уже удалила, управляемых ресурсов.Because managed resources are released nondeterministically by the garbage collector, it's not safe to refer to managed resources in a finalizer because it's possible that the garbage collector has already cleaned up that managed resource.

Метод завершения Visual C++ не является таким же, как Finalize метод.A Visual C++ finalizer is not the same as the Finalize method. (Документации по среде CLR используется метод завершения и Finalize метод как синонимы).(CLR documentation uses finalizer and the Finalize method synonymously). Finalize Метод вызывается сборщиком мусора, который вызывает каждый метод завершения в цепочке наследования класса.The Finalize method is called by the garbage collector, which invokes each finalizer in a class inheritance chain. В отличие от Visual C++ деструкторы вызов метода завершения производного класса не вызывает компилятору вызывать метод завершения подобным все базовые классы.Unlike Visual C++ destructors, a derived-class finalizer call does not cause the compiler to invoke the finalizer in all base classes.

Поскольку Microsoft C++ компилятор поддерживает детерминированного освобождения ресурсов, не следует пытаться реализовать Dispose или Finalize методы.Because the Microsoft C++ compiler supports deterministic release of resources, don't try to implement the Dispose or Finalize methods. Тем не менее, если вы знакомы с этими методами, вот как метода завершения Visual C++ и деструктор, который вызывает метод завершения сопоставляются Dispose шаблон:However, if you're familiar with these methods, here's how a Visual C++ finalizer and a destructor that calls the finalizer map to the Dispose pattern:

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

Управляемый тип может также использовать управляемые ресурсы, которые вы хотите использовать для детерминированного освобождения и не оставляют сборщику мусора освободить недетерминированным образом в некоторый момент после объект больше не требуется.A managed type may also use managed resources that you would prefer to release deterministically, and not leave to the garbage collector to release nondeterministically at some point after the object is no longer required. Детерминированного освобождения ресурсов может значительно повысить производительность.The deterministic release of resources can significantly improve performance.

Microsoft C++ компилятора позволяет определять деструктор для детерминированной очистки объектов.The Microsoft C++ compiler enables the definition of a destructor to deterministically clean up objects. Используйте деструктор для освобождения всех ресурсов, которые для детерминированного освобождения.Use the destructor to release all resources that you want to deterministically release. Если присутствует метод завершения, вызовите его из деструктора, чтобы избежать дублирования кода.If a finalizer is present, call it from the destructor, to avoid code duplication.

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

Если код, который использует ваш тип не вызывает деструктор, сборщик мусора в конечном счете освобождает все управляемые ресурсы.If the code that consumes your type does not call the destructor, the garbage collector eventually releases all managed resources.

Наличие деструктор не подразумевает наличие метода завершения.The presence of a destructor does not imply the presence of a finalizer. Тем не менее метода завершения предполагает, что необходимо определить деструктор и вызывать метод завершения из деструктора.However, the presence of a finalizer implies that you must define a destructor and call the finalizer from that destructor. Это обеспечивает для детерминированного освобождения неуправляемых ресурсов.This provides for the deterministic release of unmanaged resources.

Подавляет вызова деструктора — с помощью SuppressFinalize— финализации объекта.Calling the destructor suppresses—by using SuppressFinalize—finalization of the object. Если деструктор не вызван, метод завершения для данного типа в конечном итоге будет вызываться сборщиком мусора.If the destructor is not called, your type's finalizer will eventually be called by the garbage collector.

Детерминированной очистки ресурсов объекта путем вызова деструктора может повысить производительность по сравнению с позволяя недетерминированным образом финализации объекта среды CLR.Deterministically cleaning up your object's resources by calling the destructor can improve performance compared with letting the CLR nondeterministically finalize the object.

Код, который написан на Visual C++ и скомпилированные с помощью /CLR выполняется деструктор типа, если:Code that's written in Visual C++ and compiled by using /clr runs a type's destructor if:

Если ваш тип используется клиентом, который написан на другом языке, деструктор вызывается следующим образом:If your type is being consumed by a client that's written in another language, the destructor is called as follows:

  • Во время вызова Dispose.On a call to Dispose.

  • Во время вызова Dispose(void) на тип.On a call to Dispose(void) on the type.

  • Если тип выходит за пределы области в C# using инструкции.If the type goes out of scope in a C# using statement.

При создании объекта ссылочного типа в управляемой куче (не с помощью семантики стека для ссылочных типов), используйте try-finally синтаксис, чтобы убедиться, что исключение не не деструктор применялось.If you create an object of a reference type on the managed heap (not using stack semantics for reference types), use try-finally syntax to ensure that an exception doesn't prevent the destructor from running.

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

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

Если ваш тип содержит деструктор, компилятор создает Dispose метод, который реализует IDisposable.If your type has a destructor, the compiler generates a Dispose method that implements IDisposable. Если тип, который создается на языке Visual C++ и содержит деструктор, использования из другого языка, вызов метода IDisposable::Dispose от конкретного типа вызывает деструктор типа для вызова.If a type that's written in Visual C++ and has a destructor that's consumed from another language, calling IDisposable::Dispose on that type causes the type's destructor to be called. При использовании типа из клиента Visual C++, нельзя вызывать непосредственно Dispose; вместо этого вызова деструктора, используя delete оператор.When the type is consumed from a Visual C++ client, you can't directly call Dispose; instead, call the destructor by using the delete operator.

Если ваш тип имеет метод завершения, компилятор создает Finalize(void) метод, который переопределяет Finalize.If your type has a finalizer, the compiler generates a Finalize(void) method that overrides Finalize.

Если тип имеет метод завершения или деструктор, компилятор создает Dispose(bool) метод, согласно шаблону проектирования.If a type has either a finalizer or a destructor, the compiler generates a Dispose(bool) method, according to the design pattern. (Сведения см. в разделе шаблон Dispose).(For information, see Dispose Pattern). Нельзя явно создавать или вызвать Dispose(bool) в Visual C++.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 are called when the destructor for the derived class is called. (Если ваш тип создается на языке Visual C++, компилятор гарантирует, что типы реализации этого шаблона.) Другими словами, деструктор ссылочного класса связан его базовых классов и членов, как указано в стандарте C++ — первый, деструктор класса является выполнение, то деструкторы для членов, в обратном порядке, в котором они были созданы, и, наконец деструкторы для его базовых классов, в обратном порядке, в котором они были созданы.(If your type is written in Visual C++, the compiler ensures that your types implement this pattern.) In other words, the destructor of a reference class chains to its bases and members as specified by the C++ standard—first the class’s destructor is run, then the destructors for its members in the reverse of the order in which they were constructed, and finally the destructors for its base classes in the reverse of the order in which 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 are also called, beginning with the least derived type. Методы завершения для элементов данных не соединяются автоматически, метод завершения класса.Finalizers for data members are not automatically chained to by a class’s finalizer.

Если метод завершения удаляет собственный указатель в управляемом типе, необходимо убедиться, что ссылки, или через собственный указатель не собираются преждевременно; вызова деструктора в управляемый тип, вместо использования KeepAlive.If a finalizer deletes a native pointer in a managed type, you must ensure that references to or through the native pointer are not prematurely collected; call the destructor on the managed type instead of using KeepAlive.

Во время компиляции вы можете обнаружить, является ли тип имеет метод завершения или деструктор.At compile time, you can detect whether a type has a finalizer or a destructor. Дополнительные сведения см. в разделе поддержка характеристик типов компилятором.For more information, see Compiler Support for Type Traits.

В приведенном ниже примере показано два типа, имеющую неуправляемые ресурсы, а другой управляемые ресурсы, которые выпускаются детерминировано.The next sample shows two types, one that has unmanaged resources and one that has managed resources that are deterministically released.

// 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

Классы и структурыClasses and Structs
Классы и структурыClasses and Structs