Instrukcje: Definiowanie i korzystanie z klas i struktur (C++/CLI)How to: Define and consume classes and structs (C++/CLI)

W tym artykule pokazano, jak definiować i korzystać z typów referencyjnych zdefiniowanych przez użytkownika i typów wartości w języku C++/CLI.This article shows how to define and consume user-defined reference types and value types in C++/CLI.

Tworzenie wystąpienia obiektuObject instantiation

Typy odwołań (ref) można tworzyć tylko na stercie zarządzanym, a nie na stosie ani na stercie natywnym.Reference (ref) types can only be instantiated on the managed heap, not on the stack or on the native heap. Typy wartości można tworzyć przy użyciu stosu lub sterty zarządzanej.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;
}

Klasy abstrakcyjne niejawnieImplicitly abstract classes

Nie można utworzyć wystąpienia niejawnie klasy abstrakcyjnej .An implicitly abstract class can't be instantiated. Klasa jest abstrakcyjna niejawnie, gdy:A class is implicitly abstract when:

  • podstawowy typ klasy jest interfejsem, athe base type of the class is an interface, and
  • Klasa nie implementuje wszystkich funkcji elementów członkowskich interfejsu.the class doesn't implement all of the interface's member functions.

Nie można tworzyć obiektów z klasy, która jest pochodną interfejsu.You may be unable to construct objects from a class that's derived from an interface. Przyczyną może być niejawne abstrakcyjność klasy.The reason might be that the class is implicitly abstract. Aby uzyskać więcej informacji na temat klas abstrakcyjnych, zobacz abstract.For more information about abstract classes, see abstract.

Poniższy przykład kodu pokazuje, że MyClass nie można utworzyć wystąpienia klasy, ponieważ funkcja MyClass::func2 nie jest zaimplementowana.The following code example demonstrates that the MyClass class can't be instantiated because function MyClass::func2 isn't implemented. Aby włączyć przykład do kompilowania, Usuń komentarz 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.
}

Widoczność typuType visibility

Można kontrolować widoczność typów środowiska uruchomieniowego języka wspólnego (CLR).You can control the visibility of common language runtime (CLR) types. Gdy zestaw jest przywoływany, kontrolujesz, czy typy w zestawie są widoczne, czy nie są widoczne poza zestawem.When your assembly is referenced, you control whether types in the assembly are visible or not visible outside the assembly.

public wskazuje, że typ jest widoczny dla każdego pliku źródłowego, który zawiera #using dyrektywę dla zestawu, który zawiera typ.public indicates that a type is visible to any source file that contains a #using directive for the assembly that contains the type. private wskazuje, że typ nie jest widoczny dla plików źródłowych zawierających #using dyrektywę dla zestawu, który zawiera typ.private indicates that a type isn't visible to source files that contain a #using directive for the assembly that contains the type. Jednak typy prywatne są widoczne w tym samym zestawie.However, private types are visible within the same assembly. Domyślnie widoczność klasy to private .By default, the visibility for a class is private.

Domyślnie przed programem Visual Studio 2005 typy natywne miały publiczny dostęp poza zestawem.By default before Visual Studio 2005, native types had public accessibility outside the assembly. Włącz Ostrzeżenie kompilatora (poziom 1) C4692 , aby ułatwić sprawdzenie, gdzie prywatne typy natywne są używane nieprawidłowo.Enable Compiler Warning (level 1) C4692 to help you see where private native types are used incorrectly. Użyj dyrektywy pragma make_public , aby zapewnić publiczny dostęp do typu natywnego w pliku kodu źródłowego, którego nie można modyfikować.Use the make_public pragma to give public accessibility to a native type in a source code file that you can't modify.

Aby uzyskać więcej informacji, zobacz #using dyrektywie.For more information, see #using Directive.

Poniższy przykład pokazuje, jak zadeklarować typy i określić ich dostępność, a następnie uzyskać dostęp do tych typów wewnątrz zestawu.The following sample shows how to declare types and specify their accessibility, and then access those types inside the assembly. Jeśli do zestawu, do którego odwołuje się typ prywatny, przywoływane jest użycie #using , widoczne są tylko typy publiczne w zestawie.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();
}

Dane wyjścioweOutput

in Public_Class
in Private_Class
in Private_Class_2

Teraz ponownie napiszemy poprzedni przykład, tak aby był skompilowany jako biblioteka DLL.Now, let's rewrite the previous sample so that it's 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");}
};

Następny przykład pokazuje, jak uzyskać dostęp do typów poza zestawem.The next sample shows how to access types outside the assembly. W tym przykładzie klient korzysta ze składnika skompilowanego w poprzednim przykładzie.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;
}

Dane wyjścioweOutput

in Public_Class

Widoczność elementów członkowskichMember visibility

Dostęp do elementu członkowskiego klasy publicznej można uzyskać z poziomu tego samego zestawu innego niż dostęp do niego spoza zestawu przy użyciu par specyfikatorów dostępu public , protected i 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

Ta tabela zawiera podsumowanie wpływu różnych specyfikatorów dostępu:This table summarizes the effect of the various access specifiers:

SpecyfikatorSpecifier EfektEffect
public Element członkowski jest dostępny wewnątrz i na zewnątrz zestawu.Member is accessible inside and outside the assembly. Aby uzyskać więcej informacji, zobacz public.For more information, see public.
private Składowa jest niedostępna, zarówno wewnątrz, jak i na zewnątrz zestawu.Member is inaccessible, both inside and outside the assembly. Aby uzyskać więcej informacji, zobacz private.For more information, see private.
protected Składowa jest dostępna wewnątrz i na zewnątrz zestawu, ale tylko dla typów pochodnych.Member is accessible inside and outside the assembly, but only to derived types. Aby uzyskać więcej informacji, zobacz protected.For more information, see protected.
internal Składowa jest publiczna wewnątrz zestawu, ale jest prywatna poza zestawem.Member is public inside the assembly but private outside the assembly. internal jest kontekstowym słowem kluczowym.internal is a context-sensitive keyword. Aby uzyskać więcej informacji, zobacz kontekstowe słowa kluczowe.For more information, see Context-Sensitive Keywords.
public protected oraz protected publicpublic protected -or- protected public Składowa jest publiczna wewnątrz zestawu, ale chroniona poza zestawem.Member is public inside the assembly but protected outside the assembly.
private protected oraz protected privateprivate protected -or- protected private Składowa jest chroniona wewnątrz zestawu, ale prywatny poza zestawem.Member is protected inside the assembly but private outside the assembly.

Poniższy przykład pokazuje typ publiczny, który ma składowe, które są zadeklarowane przy użyciu różnych specyfikatorów dostępu.The following sample shows a public type that has members that are declared using the different access specifiers. Następnie wyświetla dostęp do tych członków z wnętrza zestawu.Then, it shows access to 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();
}

Dane wyjścioweOutput

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

Teraz skompilujemy poprzedni przykład jako bibliotekę 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("=======================");
   }
};

Poniższy przykład zużywa składnik, który został utworzony w poprzednim przykładzie. Pokazuje, jak uzyskać dostęp do elementów członkowskich spoza zestawu.It 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();
}

Dane wyjścioweOutput

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

Publiczne i prywatne klasy natywnePublic and private native classes

Z typem zarządzanym można odwoływać się do typu natywnego.A native type can be referenced from a managed type. Na przykład funkcja w typie zarządzanym może przyjmować parametr, którego typ jest strukturą natywną.For example, a function in a managed type can take a parameter whose type is a native struct. Jeśli typ zarządzany i funkcja są publiczne w zestawie, wówczas typem natywnym również musi być Public.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;
};

Następnie utwórz plik kodu źródłowego, który używa typu natywnego: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) {}
};

Teraz Skompiluj klienta: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);
}

Konstruktory statyczneStatic constructors

Typ CLR — na przykład Klasa lub struktura — może mieć Konstruktor statyczny, którego można użyć do zainicjowania statycznych elementów członkowskich danych.A CLR type—for example, a class or struct—can have a static constructor that can be used to initialize static data members. Konstruktor statyczny jest wywoływany najwyżej raz i jest wywoływany przed uzyskaniem dostępu do dowolnego statycznego elementu członkowskiego typu po raz pierwszy.A static constructor is called at most once, and is called before any static member of the type is accessed the first time.

Konstruktor wystąpienia zawsze jest uruchamiany po konstruktorze statycznym.An instance constructor always runs after a static constructor.

Kompilator nie może wykonać wywołania konstruktora, jeśli klasa ma Konstruktor statyczny.The compiler can't inline a call to a constructor if the class has a static constructor. Kompilator nie może wbudowana wywołania żadnej funkcji członkowskiej, jeśli klasa jest typem wartości, ma statyczny Konstruktor i nie ma konstruktora wystąpienia.The compiler can't inline a call to any member function if the class is a value type, has a static constructor, and doesn't have an instance constructor. Środowisko CLR może być wbudowane wywołanie, ale kompilator nie może.The CLR may inline the call, but the compiler can't.

Zdefiniuj statyczny Konstruktor jako prywatną funkcję członkowską, ponieważ jest przeznaczony do wywoływania tylko przez środowisko CLR.Define a static constructor as a private member function, because it's meant to be called only by the CLR.

Aby uzyskać więcej informacji na temat konstruktorów statycznych, zobacz How to: define an static Interface konstruktora (C++/CLI) .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();
}

Dane wyjścioweOutput

in static constructor
10
11

Semantyka this wskaźnikaSemantics of the this pointer

Gdy używasz języka C++ \CLI do definiowania typów, this wskaźnik w typie referencyjnym jest uchwytem typu dojście.When you're using C++\CLI to define types, the this pointer in a reference type is of type handle. this Wskaźnik w typie wartości jest typu wskaźnika wewnętrznego.The this pointer in a value type is of type interior pointer.

Te różne semantyki this wskaźnika mogą spowodować nieoczekiwane zachowanie w przypadku wywołania domyślnego indeksatora.These different semantics of the this pointer can cause unexpected behavior when a default indexer is called. W następnym przykładzie pokazano poprawny sposób dostępu do domyślnego indeksatora zarówno w typie referencyjnym, jak i w typie wartości.The next example shows the correct way to access a default indexer in both a ref type and a value type.

Aby uzyskać więcej informacji, zobacz uchwyt do operatora obiektu (^) i interior_ptr (C++/CLI)For more information, see Handle to Object Operator (^) and 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();
}

Dane wyjścioweOutput

10.89
10.89

Funkcje ukrywania za pomocą podpisuHide-by-signature functions

W standardowym języku C++ funkcja w klasie bazowej jest ukryta przez funkcję, która ma taką samą nazwę w klasie pochodnej, nawet jeśli funkcja klasy pochodnej nie ma tego samego rodzaju lub liczby parametrów.In standard C++, a function in a base class gets hidden by a function that has the same name in a derived class, even if the derived-class function doesn't have the same kind or number of parameters. Jest ona znana jako semantyka ukrycia przez nazwę .It's known as hide-by-name semantics. W typie referencyjnym funkcja w klasie bazowej jest tylko ukryta przez funkcję w klasie pochodnej, jeśli zarówno nazwa, jak i lista parametrów są takie same.In a reference type, a function in a base class only gets hidden by a function in a derived class if both the name and the parameter list are the same. Jest ona znana jako semantyka ukrycia przez podpis .It's known as hide-by-signature semantics.

Klasa jest traktowana jako Klasa ukrywania według podpisu, gdy wszystkie jej funkcje są oznaczone w metadanych jako hidebysig .A class is considered a hide-by-signature class when all of its functions are marked in the metadata as hidebysig. Domyślnie wszystkie klasy, które są tworzone w /clr programie mają hidebysig funkcje.By default, all classes that are created under /clr have hidebysig functions. Gdy Klasa ma hidebysig funkcje, kompilator nie ukrywa funkcji według nazwy w żadnej bezpośredniej klasie podstawowej, ale jeśli kompilator napotyka klasę ukrycia-by-name w łańcuchu dziedziczenia, kontynuuje działanie ukrywania przez nazwę.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.

W obszarze semantyka ukrycia po podpisie, gdy funkcja jest wywoływana w obiekcie, kompilator identyfikuje najbardziej pochodną klasę, która zawiera funkcję, która może spełnić wywołanie funkcji.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. Jeśli istnieje tylko jedna funkcja w klasie, która spełnia wywołanie, kompilator wywołuje tę funkcję.If there's only one function in the class that satisfies the call, the compiler calls that function. Jeśli w klasie jest więcej niż jedna funkcja, która może spełnić wywołanie, kompilator używa reguł rozpoznawania przeciążenia, aby określić, która funkcja ma być wywoływana.If there's more than one function in the class that could satisfy the call, the compiler uses overload resolution rules to determine which function to call. Aby uzyskać więcej informacji na temat reguł przeciążenia, zobacz przeciążanie funkcji.For more information about overload rules, see Function Overloading.

Dla danego wywołania funkcji funkcja w klasie bazowej może mieć sygnaturę, która sprawia, że jest nieco lepszym dopasowaniem niż funkcja w klasie pochodnej.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. Jeśli jednak funkcja została jawnie wywołana dla obiektu klasy pochodnej, wywoływana jest funkcja klasy pochodnej.However, if the function was explicitly called on an object of the derived class, the function in the derived class is called.

Ponieważ wartość zwracana nie jest uważana za część podpisu funkcji, funkcja klasy bazowej jest ukryta, jeśli ma taką samą nazwę i ma ten sam rodzaj i liczbę argumentów jak funkcja klasy pochodnej, nawet jeśli różni się w typie wartości zwracanej.Because the return value isn't considered part of a function's signature, a base-class function gets hidden if it has the same name and takes the same kind and number of arguments as a derived-class function, even if it differs in the type of the return value.

Poniższy przykład pokazuje, że funkcja w klasie bazowej nie jest ukryta przez funkcję w klasie pochodnej.The following sample shows that a function in a base class isn't 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();
}

Dane wyjścioweOutput

Base::Test

Następny przykład pokazuje, że kompilator języka Microsoft C++ wywołuje funkcję w najbardziej pochodnej klasie — nawet wtedy, gdy konwersja jest wymagana do dopasowania jednego lub kilku parametrów — i nie wywołuje funkcji w klasie bazowej, która jest lepszym dopasowaniem wywołania funkcji.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);
}

Dane wyjścioweOutput

Derived::Test2

Poniższy przykład pokazuje, że istnieje możliwość ukrycia funkcji nawet wtedy, gdy klasa podstawowa ma taki sam podpis jak Klasa pochodna.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);
}

Dane wyjścioweOutput

Derived::Test4
97

Kopiuj konstruktoryCopy constructors

Standard C++ oznacza, że Konstruktor kopiujący jest wywoływany, gdy obiekt jest przenoszony, w taki sposób, że obiekt jest tworzony i niszczony pod tym samym adresem.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.

Jeśli jednak funkcja, która jest skompilowana do MSIL wywołuje funkcję natywną, w której Klasa macierzysta lub więcej niż jedna — jest przenoszona przez wartość i gdzie Klasa macierzysta ma Konstruktor kopiujący lub destruktor, Konstruktor kopiujący nie jest wywoływany i obiekt jest niszczony pod innym adresem niż w miejscu, w którym został utworzony.However, when 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 or a destructor, no copy constructor is called and the object is destroyed at a different address than where it was created. Takie zachowanie może spowodować problemy, jeśli klasa ma wskaźnik do samego siebie lub jeśli kod śledzi obiekty według adresu.This behavior could cause problems if the class has a pointer into itself, or if the code is tracking objects by address.

Aby uzyskać więcej informacji, zobacz /CLR (Kompilacja środowiska uruchomieniowego języka wspólnego).For more information, see /clr (Common Language Runtime Compilation).

Poniższy przykład pokazuje, kiedy Konstruktor kopiujący nie jest generowany.The following sample demonstrates when a copy constructor isn't 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);
}

Dane wyjścioweOutput

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

Destruktory i finalizatoryDestructors and finalizers

Destruktory w typie referencyjnym mają niejednoznaczne czyszczenie zasobów.Destructors in a reference type do a deterministic clean-up of resources. Finalizatory czyścią niezarządzane zasoby i mogą być wywoływane przez destruktora lub niejednoznaczne przez moduł wyrzucania elementów bezużytecznych.Finalizers clean up unmanaged resources, and can be called either deterministically by the destructor or nondeterministically by the garbage collector. Aby uzyskać informacje o destruktorach w standardowym języku C++, zobacz destruktory.For information about destructors in standard C++, see Destructors.

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

Moduł wyrzucania elementów bezużytecznych CLR usuwa nieużywane obiekty zarządzane i zwalnia ich pamięć, gdy nie są już potrzebne.The CLR garbage collector deletes unused managed objects and releases their memory when they're no longer required. Jednak typ może korzystać z zasobów, które nie wiedzą, jak zwolnić.However, a type may use resources that the garbage collector doesn't know how to release. Te zasoby są znane jako niezarządzane zasoby (na przykład natywne uchwyty plików).These resources are known as unmanaged resources (native file handles, for example). Zalecamy wydanie wszystkich niezarządzanych zasobów w programie finalizatora.We recommend you release all unmanaged resources in the finalizer. Moduł wyrzucania elementów bezużytecznych zwalnia zarządzane zasoby w sposób niedeterministyczny, dlatego nie można bezpiecznie odwoływać się do zarządzanych zasobów w finalizatorze.The garbage collector releases managed resources nondeterministically, so it's not safe to refer to managed resources in a finalizer. Dzieje się tak, ponieważ istnieje możliwość, że moduł zbierający elementy bezużyteczne już je wyczyszczone.That's because it's possible the garbage collector has already cleaned them up.

Finalizator Visual C++ nie jest taki sam jak Finalize Metoda.A Visual C++ finalizer isn't the same as the Finalize method. (Dokumentacja środowiska CLR używa finalizatora i Finalize metody z synonimem).(CLR documentation uses finalizer and the Finalize method synonymously). FinalizeMetoda jest wywoływana przez moduł wyrzucania elementów bezużytecznych, który wywołuje każdy finalizator w łańcuchu dziedziczenia klas.The Finalize method is called by the garbage collector, which invokes each finalizer in a class inheritance chain. W przeciwieństwie do Visual C++ destruktorów, wywołanie finalizatora klasy pochodnej nie powoduje, że kompilator wywołuje finalizator we wszystkich klasach bazowych.Unlike Visual C++ destructors, a derived-class finalizer call doesn't cause the compiler to invoke the finalizer in all base classes.

Ponieważ kompilator języka Microsoft C++ obsługuje deterministyczną wersję zasobów, nie należy próbować zaimplementować Dispose metod lub Finalize .Because the Microsoft C++ compiler supports deterministic release of resources, don't try to implement the Dispose or Finalize methods. Jeśli jednak znasz te metody, poniżej przedstawiono sposób, w jaki Visual C++ finalizator i destruktor wywołujący mapę finalizatora do Dispose wzorca: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();
   }
}

Typ zarządzany może również wykorzystywać zarządzane zasoby, które wolisz zwolnić.A managed type may also use managed resources that you'd prefer to release deterministically. Nie można chcieć, aby moduł wyrzucania elementów bezużytecznych zwolnił obiekt w pewnym momencie, gdy obiekt nie jest już wymagany.You may not want the garbage collector to release an object nondeterministically at some point after the object is no longer required. Deterministyczna wersja zasobów może znacząco poprawić wydajność.The deterministic release of resources can significantly improve performance.

Kompilator języka Microsoft C++ umożliwia definiowanie destruktora do niejednoznacznego czyszczenia obiektów.The Microsoft C++ compiler enables the definition of a destructor to deterministically clean up objects. Użyj destruktora, aby zwolnić wszystkie zasoby, które mają być wydane w sposób deterministyczny.Use the destructor to release all resources that you want to deterministically release. Jeśli jest obecny finalizator, wywołaj go z destruktora, aby uniknąć duplikowania kodu.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
      // ...
   }
};

Jeśli kod, który zużywa typ, nie wywołuje destruktora, Moduł wyrzucania elementów bezużytecznych ostatecznie zwolni wszystkie zarządzane zasoby.If the code that consumes your type doesn't call the destructor, the garbage collector eventually releases all managed resources.

Obecność destruktora nie oznacza obecności finalizatora.The presence of a destructor doesn't imply the presence of a finalizer. Jednak obecność finalizatora oznacza, że należy zdefiniować destruktor i wywołać finalizator z tego destruktora.However, the presence of a finalizer implies that you must define a destructor and call the finalizer from that destructor. To wywołanie umożliwia jednoznaczne wydawanie niezarządzanych zasobów.This call provides for the deterministic release of unmanaged resources.

Wywoływanie destruktora pomijania — przy użyciu SuppressFinalize — finalizowanie obiektu.Calling the destructor suppresses—by using SuppressFinalize—finalization of the object. Jeśli destruktor nie jest wywoływany, finalizator typu będzie ostatecznie wywoływany przez moduł wyrzucania elementów bezużytecznych.If the destructor isn't called, your type's finalizer will eventually be called by the garbage collector.

Aby zwiększyć wydajność, można wywołać destruktor, aby w sposób niejednoznaczny wyczyścić zasoby obiektu, zamiast pozwolić, aby środowisko CLR w sposób niejednoznaczny zakończył obiekt.You can improve performance by calling the destructor to deterministically clean up your object's resources, instead of letting the CLR nondeterministically finalize the object.

Kod, który został zapisany w Visual C++ i skompilowany przy użyciu, /clr uruchamia destruktor typu, jeśli:Code that's written in Visual C++ and compiled by using /clr runs a type's destructor if:

Jeśli klient, który jest zapisany w innym języku zużywa swój typ, destruktor zostaje wywołany w następujący sposób:If a client that's written in another language consumes your type, the destructor gets called as follows:

  • W wywołaniu Dispose .On a call to Dispose.

  • W wywołaniu Dispose(void) typu.On a call to Dispose(void) on the type.

  • Jeśli typ wykracza poza zakres w instrukcji języka C# using .If the type goes out of scope in a C# using statement.

Jeśli nie korzystasz z semantyki stosu dla typów referencyjnych i utworzysz obiekt typu referencyjnego na stercie zarządzanym, użyj składni try-finally , aby upewnić się, że wyjątek nie uniemożliwia uruchomienia destruktora.If you're not using stack semantics for reference types and create an object of a reference type on the managed heap, 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;
   }
}

Jeśli typ ma destruktor, kompilator generuje Dispose metodę implementującą IDisposable .If your type has a destructor, the compiler generates a Dispose method that implements IDisposable. Jeśli typ, który jest pisany w Visual C++ i ma destruktor, który jest używany z innego języka, wywołanie IDisposable::Dispose tego typu powoduje wywoływanie destruktora typu.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. Gdy typ jest używany z klienta Visual C++, nie można bezpośrednio wywołać metody Dispose ; zamiast tego należy wywołać destruktor przy użyciu delete operatora.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.

Jeśli typ ma finalizator, kompilator generuje Finalize(void) metodę, która zastąpi Finalize .If your type has a finalizer, the compiler generates a Finalize(void) method that overrides Finalize.

Jeśli typ ma finalizator lub destruktor, kompilator generuje Dispose(bool) metodę, zgodnie ze wzorcem projektu.If a type has either a finalizer or a destructor, the compiler generates a Dispose(bool) method, according to the design pattern. (Aby uzyskać więcej informacji, zobacz artykuł usuwanie wzorców).(For information, see Dispose Pattern). Nie można jawnie tworzyć ani wywoływać Dispose(bool) w Visual C++.You can't explicitly author or call Dispose(bool) in Visual C++.

Jeśli typ ma klasę bazową, która jest zgodna ze wzorcem projektu, destruktory dla wszystkich klas podstawowych są wywoływane, gdy wywoływana jest destruktor klasy pochodnej.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. (Jeśli typ jest zapisywana w Visual C++, kompilator zapewnia, że typy implementują ten wzorzec.) Innymi słowy, destruktor klasy referencyjnej łańcuchuje do jej baz i członków zgodnie z definicją w standardzie 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. Najpierw jest uruchamiany destruktor klasy.First, the class’s destructor is run. Następnie destruktory dla swoich elementów członkowskich są uruchamiane w odwrotnej kolejności, w której zostały skonstruowane.Then, the destructors for its members get run in the reverse of the order in which they were constructed. Na koniec destruktory dla jego klas bazowych są uruchamiane w odwrocie od kolejności, w której zostały skonstruowane.Finally, the destructors for its base classes get run in the reverse of the order in which they were constructed.

Destruktory i finalizatory nie są dozwolone wewnątrz typów wartości lub interfejsów.Destructors and finalizers aren't allowed inside value types or interfaces.

Finalizator może być zdefiniowany lub zadeklarowany tylko w typie referencyjnym.A finalizer can only be defined or declared in a reference type. Podobnie jak Konstruktor i destruktor, finalizator nie ma zwracanego typu.Like a constructor and destructor, a finalizer has no return type.

Po uruchomieniu finalizatora obiektu finalizatory w każdej klasie bazowej są również wywoływane, zaczynając od najmniej typu pochodnego.After an object's finalizer runs, finalizers in any base classes are also called, beginning with the least derived type. Finalizatory elementów członkowskich danych nie są automatycznie łańcuchowe przez finalizator klasy.Finalizers for data members aren't automatically chained to by a class’s finalizer.

Jeśli finalizator usuwa wskaźnik natywny w typie zarządzanym, należy się upewnić, że odwołania do lub przez wskaźnik natywny nie są przedwcześnie zbierane.If a finalizer deletes a native pointer in a managed type, you must ensure that references to or through the native pointer aren't prematurely collected. Wywołaj destruktor w typie zarządzanym zamiast używać KeepAlive .Call the destructor on the managed type instead of using KeepAlive.

W czasie kompilacji można wykryć, czy typ ma finalizator lub destruktor.At compile time, you can detect whether a type has a finalizer or a destructor. Aby uzyskać więcej informacji, zobacz Obsługa kompilatora dla cech typu.For more information, see Compiler Support for Type Traits.

Następny przykład przedstawia dwa typy: jeden, który ma niezarządzane zasoby i jeden, który ma zarządzane zasoby, które są wystawione w sposób deterministyczny.The next sample shows two types: one that has unmanaged resources, and one that has managed resources that get released deterministically.

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

Zobacz takżeSee also

Klasy i strukturyClasses and structs