다음을 통해 공유


방법: 클래스 및 구조체를 인스턴스화할

이 글에서는 C++/CLI에서 값 형식과 사용자 정의 참조 형식을 어떻게 정의하고 사용하는지에 대해 나와있습니다.

콘텐츠

개체 인스턴스화

암시적 추상 클래스

형식 표시

멤버 표시

Public and Private native classes

정적 생성자

this 포인터의 의미 체계

Hide by signatrue functions

복사 생성자

소멸자 및 종료자

개체 인스턴스화

참조 형식(ref)이나 값 형식은 네이티브 힙이나 스택이 아닌 관리 된 힙에서만 인스턴스화 할 수 있습니다.

// 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 형식은 동일한 어셈블리 내 에서는 표시 됩니다. 기본적으로 클래스에 대한 가시성은 private 입니다.

Visual C++ 2005 이전에는 기본적으로 네이티브 형식이 public으로 어셈블리 외부에서 접근이 가능했습니다. 컴파일러 경고 (수준 1) C4692 는 private 네이티브 형식이 어디서 잘못 쓰여지고 있는지 알 수 있도록 하는데 도움이 됩니다. make_public pragma를 사용하여 수정할 수 없는 소스 코드 파일의 네이티브 형식에 public 접근 가능성을 제공하십시오.

자세한 내용은 #using 지시문 (C++)을 참조하십시오.

아래 예시에서는 형식을 선언하고 해당 접근 가능성을 지정하고 난 후 어셈블리 내에서 이러한 형식에 접근하는 방법을 보여 줍니다. 물론 #using 을 사용하여 private 형식을 가진 어셈블리를 참조하는 경우 어셈블리 내에서 오직 public 형식만 표시 됩니다.

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

  

이제 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;
}

Output

  

멤버 표시

public , protected private 와 같은 접근 지정자를 사용하여 어셈블리 외부의 접근과는 다른, 같은 어셈블리 내에서 public 클래스의 멤버에 대한 접근을 설정 할 수 있습니다.

이 표에서는 다양한 액세스 지정자의 효과를 보여 줍니다.

지정자

효과

public

멤버는 내부와 외부의 어셈블리에서 액세스할 수 있습니다. 자세한 내용은 public (C++)를 참조하십시오.

private

멤버는 어셈블리 내부와 외부 모두에서 액세스할 수 없습니다. 자세한 내용은 private (C++)를 참조하십시오.

protected

멤버는 파생된 형식에 한해 어셈블리 내부와 외부에서 접근 할 수 있습니다. 자세한 내용은 protected (C++)를 참조하십시오.

internal

멤버는 어셈블리 내부에서는 public 이지만 어셈블리 외부에서는 private 입니다. internal 문맥적인 키워드입니다. 자세한 내용은 상황에 맞는 키워드을 참조하십시오.

public protected
-or-
protected public

멤버는 어셈블리 내부에서 public 이지만 어셈블리 외부에서는 protected 입니다.

private protected
-or-
protected private

멤버는 어셈블리 내부에서 public 이지만 어셈블리 외부에서는 private 입니다.

아래 예제에서는 다른 접근 가능성이 선언된 멤버를 가진 public 형식을 보여주고 어셈블리 외부에서의 멤버들의 접근을 보여줍니다.

// type_member_visibility.cpp
// 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

  

이제 DLL로 이전 샘플을 빌드 합니다.

// type_member_visibility_2.cpp
// 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("=======================");
   }
};

다음 예제는 이전 샘플에서 만들어진 구성 요소를 사용하고 있습니다. 그러므로 어셈블리 외부에서 멤버에게 어떻게 접근하는지를 보여줍니다.

// type_member_visibility_3.cpp
// 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

  

Public and Private native classes

관리 되는 형식에서 네이티브 형식은 참조 될 수 있습니다. 예를 들어, 관리 되는 형식에서 함수는 네이티브 구조체 형식의 매개변수를 가질 수 있습니다. 관리되는 형식과 함수가 어셈블리 내부에서 public이면 네이티브 형식도 반드시 public 이어야 합니다.

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

다음으로 네이티브 형식을 사용하는 소스 코드 파일을 생성합니다:

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

이제 클라이언트를 컴파일하십시오.

// mcppv2_ref_class4.cpp
// 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에 의해서만 호출 될 수 있도록 정적 생성자를 private 멤버 함수로 선언 하십시오.

정적 생성자에 대한 자세한 내용은 방법: 인터페이스 정적 생성자 정의(C++/CLI) 를 참조하십시오.

// mcppv2_ref_class6.cpp
// 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

  

this pointer의 의미체계

Visual C++ 을 사용해서 형식을 정의 하는 경우 참조 형식 내의 this 포인터는 "handle" 입니다. 값 형식에서 this 포인터는 "interior point' 입니다.

이러한 서로 다른 의미의 this 포인터는 기본 인덱서가 호출 될 때 예기치 못한 동작이 발생할 수 있습니다. 다음 예제에서는 참조 형식과 값 형식에서 기본 인덱서에 액세스 하기 위한 올바른 방법을 보여 줍니다.

자세한 내용은 다음을 참조하십시오.

// semantics_of_this_pointer.cpp
// 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

  

Hide by signatrue functions

표준 C++에서 베이스 클래스 내의 함수는 비록 매개 변수의 종류나 개수가 같지 않아도 파생된 클래스에서 같은 이름을 가진 함수에 의해 숨겨져 있습니다. 이 것을 hide-by -name 이라고 합니다. 참조 형식에서 베이스 클래스의 함수에 대한 파생 클래스의 함수의 이름과 매개변수의 목록이 모두 같다면 이 함수에 의해 베이스 클래스의 함수는 숨겨질 수 있습니다. 이것은 hide-by-signature 을 의미합니다.

모든 함수가 메타데이터내에서 hidebysig 로 표시되면 클래스는 hide-byd-signature 클래스라고 간주 됩니다. 기본적으로 모든 클래스는 hidebysig 를 가진 /clr 아래에 생성 됩니다. 그러나 /clr:oldSyntax 에 의해 컴파일 된 클래스는 hidebysig 을 가지고 있지 않습니다. 대신 해당 클래스는 hide-by-name 함수입니다. 클래스가 hidebysig 함수를 가지고 있을때 컴파일러는 함수를 직접 기본 클래스의 이름으로 숨기지 않습니다. 하지만 컴파일러가 상속 체인의 hide-by-name 클래스를 만나면 hide-by-name 동작을 유지합니다.

hide-by-signature의 의미에서 함수가 객체에 의해 호출되면 컴파일러는 함수 호출을 충족시켜 줄 수 있는 함수를 포함한 파생 클래스 중에서 가장 많이 파생 된 클래스를 식별합니다. 만약 호출을 충족시켜줄 수 있는 클래스가 하나 뿐이라면 컴파일러는 그 함수를 호출 합니다. 호출을 충족할 수 있는 클래스에 두 개 이상의 함수가 있으면 컴파일러는 오버로드 결정 규칙을 사용하여 함수를 결정합니다. 오버로드 규칙에 대한 자세한 내용은 함수 오버로드를 참조하십시오.

주어진 함수 호출에 대해 기본 클래스안의 함수는 파생 클래스의 함수보다 보다 조금 더 나은 시그니쳐를 가지고 있을 수 있습니다. 그러나 파생 클래스의 객체에서 명시적으로 호출 된 함수의 경우 파생 클래스의 함수가 호출 됩니다.

그것은 반환 값이 함수의 시그니쳐의 일부분으로 간주 되지 않기 때문에 비록 변환 값의 형식이 달라도 기본 클래스가 파생클래스의 함수와 같은 이름과 같은 개수와 종류의 매개변수를 가지고 있다면 기본 클래스는 숨겨집니다.

다음 예제에서는 기본 클래스의 함수가 파생된 클래스의 함수에 의해 숨겨지지 않는 것을 보여 줍니다.

// hide_by_signature_1.cpp
// 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

  

다음 예제에서는 Visual C++에서 컴파일러가 비록 매개 변수가 하나 이상 일치 하기 위해 변환이 요구 되어도 가장 많이 파생된 클래스의 함수를 호출하는 것을 보여주고, 함수 호출에 대해 더 일치하는 베이스 클래스의 함수를 호출 하지 않는 것을 보여 줍니다.

// hide_by_signature_2.cpp
// 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

  

다음 예제는 기본 클래스가 파생된 클래스와 같은 시그니쳐를 가져도 함수를 숨길 수 있음을 보여 줍니다.

// hide_by_signature_3.cpp
// 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

  

다음 예제는 /clr:oldSyntax을 이용해 컴파일된 구성요소를 정의합니다.. Managed Extensions for C++ 을 사용하여 정의된 클래스는 hide-by-name 멤버 함수를 가집니다.

// hide_by_signature_4.cpp
// compile with: /clr:oldSyntax /LD
using namespace System;
public __gc struct Base0 {
   void Test() { 
      Console::WriteLine("in Base0::Test");
   }
};

public __gc struct Base1 : public Base0 {
   void Test(int i) { 
      Console::WriteLine("in Base1::Test");
   }
};

다음 예제에서는 이전 예제에서 빌드 되었던 구성요소를 사용합니다. hide-by-signature 기능은 /clr:oldSyntax에 의해 컴파일 된 형식의 기본 클래스 에 적용되지 않습니다.

// hide_by_signature_5.cpp
// compile with: /clr:oldSyntax /LD
// compile with: /clr
using namespace System;
#using "hide_by_signature_4.dll"

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

int main() {
   Derived ^ t = gcnew Derived;
   t->Test(8, 8);   // OK
   t->Test(8);   // OK
   t->Test();   // C2661
}

복사 생성자

표준 C++ 에 따르면 복사 생성자는 개체가 같은 주소에서 생성되고 삭제되는 것처럼 개체가 이동하면 호출 됩니다.

그러나 /clr 이 컴파일을 하기 위해 사용되고 네이티브 클래스—두 개 이상의—에 있는 네이티브 함수에 대한 MSIL의 호출로 컴파일 된 함수가 값에 의해 전달 되고 네이티브 클래스가 복사 생성자나 소멸자를 가지고 있는 경우, 복사 생성자는 호출 되지 않고 개체가 어디서 생성 되었든 다른 주소에서 개체를 삭제 합니다. 클래스에는 자체에 대한 포인터를 가지고 있거나 또는 코드가 주소에 의해 개체를 추적하는 경우 문제가 발생할 수 있습니다.

자세한 내용은 /clr(공용 언어 런타임 컴파일)을 참조하십시오.

다음 예제에서는 언제 복사 생성자가 생성 되지 않는지 보여줍니다.

// breaking_change_no_copy_ctor.cpp
// 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

  

소멸자 및 종료자

참조 형식에서 소멸자는 명확한 정리 리소스를 수행합니다. 종료자는 관리 되지 않는 리소스를 정리하고 소멸자에 의해 명확하게 호출 될 수 있으며, 가비지 수집기에 의해 불명확하게 호출 될 수 있습니다. 표준 C++ 에서 소멸자에 대한 자세한 내용은 소멸자 (C++) 을 참고하십시오.

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

관리 되는 Visual C++클래스의 소멸자와 Managed Extensions for C++의 소멸자는 동작이 다릅니다. 이 변경 내용에 대한 자세한 내용은 소멸자 의미의 변경 내용를 참조하십시오.

CLR 가비지 수집기는 사용되지 않는 관리 된 개체를 삭제하고 필요없어진 메모리를 해제 합니다. 그러나 형식이 가비지 수집기가 해제 하는 방법을 알지 못하는 리소스를 사용 할 수도 있습니다. 이러한 리소스를 관리 되지 않는 리소스라고(예를 들어, native file handles)이라고 합니다. 종료자에서 관리 되지 않는 리소스는 모두 해제 하는 것이 좋습니다. 관리 되는 리소스는 가비지 수집기에 의해 불명확하게 해제 되기 때문에 종료자에서 관리되는 리소스가 안전하다고 할 수 없습니다. 이는 가비지 수집기가 이미 관리되는 리소스를 정리했을 수도 있기 때문입니다.

Visual C++ 종료자와 Finalize 는 같지 않습니다. (CLR documentation은 종료자와 Finalize 메서드를 같은 뜻으로 사용합니다.) Finalize 메서드는 클래스 상속 체인에 있는 각 종료자를 호출하는 가비지 수집기에 의해 호출 됩니다. Visual C++ 소멸자와 달리 파생 클래스의 종료자 호출은 컴파일러에 기본클래스의 종료자 호출을 발생시키지 않습니다.

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

만약 명확하게 해제시키고 싶다면 관리된 형식은 관리된 리소스를 사용할 수도 있습니다. 그리고 개체가 더이상 필요 하지 않게된 이후에 불명확하게 해제하기 위해 가비지 수집기에 넘기지 않아도 됩니다. 리소스의 명확한 해제는 성능을 상당히 개선할 수 있습니다.

Visual C++ 컴파일러는 개체의 명확한 정리를 위해 소멸자의 정의를 사용할 수 있습니다. 소멸자를 사용하여 원하는 리소스를 명확하게 해제하십시오. 코드 중복을 피하기 위해 종료자가 존재하면 소멸자에서 호출 하십시오.

// destructors_finalizers_1.cpp
// compile with: /clr /c
ref struct A {
   // destructor cleans up all resources
   ~A() {
      // clean up code to release managed resource
      // ...
      // to avoid code duplication, 
      // call finalizer to release unmanaged resources
      this->!A();
   }

   // finalizer cleans up unmanaged resources
   // destructor or garbage collector will
   // clean up managed resources
   !A() {
      // clean up code to release unmanaged resources
      // ...
   }
};

형식을 사용하는 코드가 소멸자를 호출하지 않으면 가비지 수집기는 결국 관리 되는 리소스를 모두 해제 합니다.

소멸자의 존재가 종료자의 존재를 의미하지는 않습니다. 그러나 종료자가 있으면 소멸자를 반드시 정의해야 하고 해당 소멸자에서 종료자를 호출 해야 함을 의미 합니다. 이것은 관리되지 않은 리소스의 명확한 해제를 제공합니다.

SuppressFinalize 을 사용해 소멸자를 호출하여 개체의 종료를 막습니다. 소멸자가 호출 되지 않으면 해당 형식의 종료자가 결국 가비지 수집기에 의해 호출 됩니다.

소멸자를 호출하여 명확하게 개체의 리소스를 정리하는 것은 CLR이 불명확하게 개체를 종료하는 것에 비해 성능을 향상 시킬 수 있습니다.

Visual C++ 로 작성하고 /clr 을 이용하여 컴파일 된 코드는 형식의 소멸자를 실행 합니다. 만약 :

다른 언어로 작성 된 클라이언트에 의해 형식이 사용되는 중인 경우 소멸자가 아래와 같이 호출 됩니다.

  • Dispose 에서의 호출인 경우

  • Dispose(void) 에 대한 호출인 경우.

  • 형식이 C#의 using 문 안에서 범위를 벗어나는 경우

만약 관리되는 힙에서 (참조 형식에 스택 의미를 사용하지 않고) 참조 형식의 개체를 생성할 경우, try-finally 문을 사용하여 실행중에 예외가 소멸자를 방지 하지 않도록 확인 하십시오.

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

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

형식에 소멸자를 가지고 있다면 컴파일은 IDisposable 를 구현하는 Dispose 메서드를 생산합니다. Visual C++ 에 의해 작성되고 다른 언어에서 사용된 소멸자가있는 형식인 경우 해당 형식에서 IDisposable::Dispose 를 호출하는 것은 호출된 해당 소멸자를 실행 시킬 수 있습니다. 형식이 Visual C++ 클라이언트에서 사용되면 Dispose 를 직접 호출 할 수 없습니다. 대신 delete 를 사용하여 소멸자를 호출합니다.

형식에 종료자가 있는 경우 컴파일러는 Finalize 를 오버라이드 하는 Finalize(void) 메서드를 생성합니다.

형식이 종료자 또는 소멸자를 가지는 경우 컴파일러는 디자인 패턴에 따라 Dispose(bool) 메서드를 생성합니다. (자세한 내용은 Implementing Finalize and Dispose to Clean Up Unmanaged Resources를 참조하십시오.) Visual C++에서 Dispose(bool) 를 명시적으로 작성하거나 호출할 수 없습니다.

형식이 디자인 패턴을 따르는 기본 클래스를 가진 경우 파생된 클래스의 소멸자가 호출 되었을 때 모든 기본 클래스의 소멸자가 호출 됩니다. (Visual C++에서 형식이 작성 된 경우 컴파일러는 형식이 이 패턴을 구현했는지 확인합니다.) 다시 말해서 참조 클래스의 소멸자는 C++에 의해 지정된 해당 베이스와 멤버에 연결 됩니다. 먼저 클래스의 소멸자가 실행되면 그 후에 생성된 순서의 반대로 해당 멤버의 소멸자가 실행됩니다. 마지막으로 해당 기본 클래스의 소멸자가 생성된 순서의 반대로 실행됩니다.

소멸자 및 종료자는 인터페이스나 값 형식에서는 허용되지 않습니다.

종료자는 오직 참조 형식에서만 선언되거나 정의될 수 있습니다. 생성자와 소멸자와 같이 종료자는 리턴 값이 없습니다.

개체의 종료자가 실행된 후 기본 클래스의 종료자도 최소 파생된 형식을 사용해 시작하며 호출됩니다. 데이터 멤버의 종료자는 클래스의 종료자에 의해 자동적으로 연결되지 않습니다.

종료자가 관리되는 형식에서 네이티브 포인터를 삭제한 경우 반드시 네이티브 포인터를 통한 참조가 이미 수집 되었는지 확인 하고 KeepAlive 을 사용하는 대신에 관리되는 형식에서 소멸자를 호출합니다.

컴파일 타임에 형식이 소멸자나 종료자를 가지고 있는지 검색할 수 있습니다. 자세한 내용은 형식 특성에 대 한 컴파일러 지원을 참조하십시오.

다음 예제는 관리되지 않은 리소스를 가진 형식과 명확하게 해제되는 관리된 리소스를 가진 형식을 보여줍니다.

// destructors_finalizers_2.cpp
// compile with: /clr
#include <vcclr.h>
#include <stdio.h>
using namespace System;
using namespace System::IO;

ref class SystemFileWriter {
   FileStream ^ file;
   array<Byte> ^ arr;
   int bufLen;

public:
   SystemFileWriter(String ^ name) : file(File::Open(name, FileMode::Append)), 
                                     arr(gcnew array<Byte>(1024)) {}

   void Flush() {
      file->Write(arr, 0, bufLen);
      bufLen = 0;
   }

   ~SystemFileWriter() {
      Flush();
      delete file;
   }
};

ref class CRTFileWriter {
   FILE * file;
   array<Byte> ^ arr;
   int bufLen;

   static FILE * getFile(String ^ n) {
      pin_ptr<const wchar_t> name = PtrToStringChars(n);
      FILE * ret = 0;
      _wfopen_s(&ret, name, L"ab");
      return ret;
   }

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

   void Flush() {
      pin_ptr<Byte> buf = &arr[0];
      fwrite(buf, 1, bufLen, file);
      bufLen = 0;
   }

   ~CRTFileWriter() {
      this->!CRTFileWriter();
   }

   !CRTFileWriter() {
      Flush();
      fclose(file);
   }
};

int main() {
   SystemFileWriter w("systest.txt");
   CRTFileWriter ^ w2 = gcnew CRTFileWriter("crttest.txt");
}

참고 항목

참조

클래스 및 구조체 (관리)

클래스 및 구조체 (관리)