如何:定義和使用類別和結構 (C++/CLI)
本文說明如何在 C++/CLI 中定義及取用使用者定義的參考型別和實值型別。
物件具現化
參考 (ref) 類型只能在 Managed 堆積上具現化,而不是在堆疊或原生堆積上具現化。 實值型別可以在堆疊或 Managed 堆積上具現化。
// 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;
}
隱含抽象類別
無法具現化隱含抽象類別 。 類別在:
- 類別的基底類型是介面,和
- 類別不會實作介面的所有成員函式。
您可能無法從衍生自 介面的類別建構物件。 原因可能是類別是隱含抽象的。 如需抽象類別的詳細資訊,請參閱 abstract 。
下列程式碼範例示範 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.
}
型別可視性
您可以控制 Common Language Runtime (CLR) 類型的可見度。 參考元件時,您可以控制元件中的類型是否可見於元件外部。
public
表示任何來源檔案可看見類型,其中包含 #using
包含型別之元件的 指示詞。 private
表示來源檔案看不到類型,而來源檔案包含包含 #using
型別之元件的 指示詞。 不過,私用型別會顯示在相同的元件內。 根據預設,類別的可見度為 private
。
根據預設,Visual Studio 2005 之前,原生類型在元件外部具有公用協助工具。 啟用 編譯器警告 (層級 1) C4692 ,以協助您查看私人原生類型使用不正確的位置。 使用 make_public pragma,為您無法修改的原始程式碼檔案中的原生類型提供公用協助工具。
如需詳細資訊,請參閱 #using 指示詞。
下列範例示範如何宣告類型並指定其協助工具,然後存取元件內的這些類型。 如果使用 參考 #using
具有私用型別的元件,則只有元件中的公用類型可見。
// type_visibility.cpp
// compile with: /clr
using namespace System;
// public type, visible inside and outside assembly
public ref struct Public_Class {
void Test(){Console::WriteLine("in Public_Class");}
};
// private type, visible inside but not outside assembly
private ref struct Private_Class {
void Test(){Console::WriteLine("in Private_Class");}
};
// default accessibility is private
ref class Private_Class_2 {
public:
void Test(){Console::WriteLine("in Private_Class_2");}
};
int main() {
Public_Class ^ a = gcnew Public_Class;
a->Test();
Private_Class ^ b = gcnew Private_Class;
b->Test();
Private_Class_2 ^ c = gcnew Private_Class_2;
c->Test();
}
輸出
in Public_Class
in Private_Class
in Private_Class_2
現在,讓我們重寫先前的範例,讓它建置為 DLL。
// type_visibility_2.cpp
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref struct Public_Class {
void Test(){Console::WriteLine("in Public_Class");}
};
// private type, visible inside but not outside the assembly
private ref struct Private_Class {
void Test(){Console::WriteLine("in Private_Class");}
};
// by default, accessibility is private
ref class Private_Class_2 {
public:
void Test(){Console::WriteLine("in Private_Class_2");}
};
下一個範例示範如何存取元件外部的類型。 在此範例中,用戶端會取用先前範例中建置的元件。
// type_visibility_3.cpp
// compile with: /clr
#using "type_visibility_2.dll"
int main() {
Public_Class ^ a = gcnew Public_Class;
a->Test();
// private types not accessible outside the assembly
// Private_Class ^ b = gcnew Private_Class;
// Private_Class_2 ^ c = gcnew Private_Class_2;
}
輸出
in Public_Class
成員可見度
您可以使用存取規範 public
protected
、 和 配對,從與從元件外部存取的相同元件存取公用類別的成員進行存取 private
下表摘要說明各種存取規範的效果:
規範 | 影響 |
---|---|
public |
成員可在元件內外存取。 如需詳細資訊,請參閱public 。 |
private |
成員在元件內外都無法存取。 如需詳細資訊,請參閱private 。 |
protected |
成員可在元件內外存取,但只能存取衍生型別。 如需詳細資訊,請參閱protected 。 |
internal |
成員在元件內部是公用的,但在元件外部是私用的。 internal 是即時線上關鍵字。 如需詳細資訊,請參閱內容相關性關鍵字。 |
public protected -或- protected public |
成員在元件內部為公用,但在元件外部受到保護。 |
private protected -或- protected private |
成員在元件內部受到保護,但在元件外部是私用的。 |
下列範例顯示具有使用不同存取規範宣告之成員的公用類型。 然後,它會從元件內顯示這些成員的存取權。
// compile with: /clr
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
void Public_Function(){System::Console::WriteLine("in Public_Function");}
private:
void Private_Function(){System::Console::WriteLine("in Private_Function");}
protected:
void Protected_Function(){System::Console::WriteLine("in Protected_Function");}
internal:
void Internal_Function(){System::Console::WriteLine("in Internal_Function");}
protected public:
void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}
public protected:
void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}
private protected:
void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}
protected private:
void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
void Test() {
Console::WriteLine("=======================");
Console::WriteLine("in function of derived class");
Protected_Function();
Protected_Private_Function();
Private_Protected_Function();
Console::WriteLine("exiting function of derived class");
Console::WriteLine("=======================");
}
};
int main() {
Public_Class ^ a = gcnew Public_Class;
MyClass ^ b = gcnew MyClass;
a->Public_Function();
a->Protected_Public_Function();
a->Public_Protected_Function();
// accessible inside but not outside the assembly
a->Internal_Function();
// call protected functions
b->Test();
// not accessible inside or outside the assembly
// a->Private_Function();
}
輸出
in Public_Function
in Protected_Public_Function
in Public_Protected_Function
in Internal_Function
=======================
in function of derived class
in Protected_Function
in Protected_Private_Function
in Private_Protected_Function
exiting function of derived class
=======================
現在讓我們建置先前的範例作為 DLL。
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
void Public_Function(){System::Console::WriteLine("in Public_Function");}
private:
void Private_Function(){System::Console::WriteLine("in Private_Function");}
protected:
void Protected_Function(){System::Console::WriteLine("in Protected_Function");}
internal:
void Internal_Function(){System::Console::WriteLine("in Internal_Function");}
protected public:
void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}
public protected:
void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}
private protected:
void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}
protected private:
void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
void Test() {
Console::WriteLine("=======================");
Console::WriteLine("in function of derived class");
Protected_Function();
Protected_Private_Function();
Private_Protected_Function();
Console::WriteLine("exiting function of derived class");
Console::WriteLine("=======================");
}
};
下列範例會取用在上一個範例中建立的元件。 它示範如何從元件外部存取成員。
// compile with: /clr
#using "type_member_visibility_2.dll"
using namespace System;
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
void Test() {
Console::WriteLine("=======================");
Console::WriteLine("in function of derived class");
Protected_Function();
Protected_Public_Function();
Public_Protected_Function();
Console::WriteLine("exiting function of derived class");
Console::WriteLine("=======================");
}
};
int main() {
Public_Class ^ a = gcnew Public_Class;
MyClass ^ b = gcnew MyClass;
a->Public_Function();
// call protected functions
b->Test();
// can't be called outside the assembly
// a->Private_Function();
// a->Internal_Function();
// a->Protected_Private_Function();
// a->Private_Protected_Function();
}
輸出
in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================
公用和私人原生類別
原生類型可以從 Managed 型別參考。 例如,Managed 型別中的函式可以接受類型為原生結構的參數。 如果 Managed 類型和函式在元件中為公用,則原生類型也必須是公用類型。
// native type
public struct N {
N(){}
int i;
};
接下來,建立使用原生類型的原始程式碼檔案:
// compile with: /clr /LD
#include "mcppv2_ref_class3.h"
// public managed type
public ref struct R {
// public function that takes a native type
void f(N nn) {}
};
現在,編譯用戶端:
// compile with: /clr
#using "mcppv2_ref_class3.dll"
#include "mcppv2_ref_class3.h"
int main() {
R ^r = gcnew R;
N n;
r->f(n);
}
靜態建構函式
CLR 類型,例如類別或結構,可以有靜態建構函式,可用來初始化靜態資料成員。 靜態建構函式最多會呼叫一次,並在第一次存取類型的任何靜態成員之前呼叫。
實例建構函式一律會在靜態建構函式之後執行。
如果類別具有靜態建構函式,編譯器就無法內嵌對建構函式的呼叫。 如果類別是實值型別、具有靜態建構函式,而且沒有實例建構函式,編譯器就無法內嵌對任何成員函式的呼叫。 CLR 可能會內嵌呼叫,但編譯器無法內嵌。
將靜態建構函式定義為私用成員函式,因為它僅供 CLR 呼叫。
如需靜態建構函式的詳細資訊,請參閱 如何:定義介面靜態建構函式 (C++/CLI) 。
// compile with: /clr
using namespace System;
ref class MyClass {
private:
static int i = 0;
static MyClass() {
Console::WriteLine("in static constructor");
i = 9;
}
public:
static void Test() {
i++;
Console::WriteLine(i);
}
};
int main() {
MyClass::Test();
MyClass::Test();
}
輸出
in static constructor
10
11
指標的 this
語意
當您使用 C++\CLI 來定義類型時, this
參考型別中的指標是型別 控制碼 。 this
實值型別中的指標是內部指標 類型 。
呼叫預設索引子時, this
指標的這些不同語意可能會導致非預期的行為。 下一個範例顯示存取 ref 型別和實值型別中預設索引子的正確方式。
如需詳細資訊,請參閱 物件運算子的控制碼 (^) 和 interior_ptr (C++/CLI)
// compile with: /clr
using namespace System;
ref struct A {
property Double default[Double] {
Double get(Double data) {
return data*data;
}
}
A() {
// accessing default indexer
Console::WriteLine("{0}", this[3.3]);
}
};
value struct B {
property Double default[Double] {
Double get(Double data) {
return data*data;
}
}
void Test() {
// accessing default indexer
Console::WriteLine("{0}", this->default[3.3]);
}
};
int main() {
A ^ mya = gcnew A();
B ^ myb = gcnew B();
myb->Test();
}
輸出
10.89
10.89
依簽章隱藏的函式
在標準 C++ 中,基類中的函式會由衍生類別中具有相同名稱的函式隱藏,即使衍生類別函式沒有相同的類型或參數數目也一樣。 其稱為 「依名稱 隱藏」語意。 在參考型別中,如果名稱和參數清單相同,基類中的函式只會由衍生類別中的函式隱藏。 其稱為 「依簽章 隱藏」語意。
當所有函式在中繼資料中標示為 hidebysig
時,類別會被視為隱藏簽章類別。 根據預設,在 底下 /clr
建立的所有類別都有 hidebysig
函式。 當類別具有 hidebysig
函式時,編譯器不會依名稱隱藏任何直接基類中的函式,但如果編譯器在繼承鏈結中遇到依名稱隱藏類別,則會繼續隱藏依名稱的行為。
在依簽章隱藏語意下,當物件上呼叫函式時,編譯器會識別包含可滿足函式呼叫之函式的最衍生類別。 如果 類別中只有一個符合呼叫的函式,編譯器會呼叫該函式。 如果 類別中有一個以上的函式可以滿足呼叫,編譯器會使用多載解析規則來判斷要呼叫的函式。 如需多載規則的詳細資訊,請參閱 函數多載 。
對於指定的函式呼叫,基類中的函式可能會有簽章,使其比衍生類別中的函式稍微比對。 不過,如果在衍生類別的物件上明確呼叫函式,則會呼叫衍生類別中的函式。
因為傳回值不被視為函式簽章的一部分,所以如果基類函式具有相同的名稱,並且接受與衍生類別函式相同的種類和引數數目,即使它與傳回值的類型不同,仍會隱藏。
下列範例顯示基類中的函式不會由衍生類別中的函式隱藏。
// compile with: /clr
using namespace System;
ref struct Base {
void Test() {
Console::WriteLine("Base::Test");
}
};
ref struct Derived : public Base {
void Test(int i) {
Console::WriteLine("Derived::Test");
}
};
int main() {
Derived ^ t = gcnew Derived;
// Test() in the base class will not be hidden
t->Test();
}
輸出
Base::Test
下一個範例顯示 Microsoft C++ 編譯器會呼叫最衍生類別中的函式,即使需要轉換以符合一或多個參數,也不會在基類中呼叫較符合函式呼叫的函式。
// compile with: /clr
using namespace System;
ref struct Base {
void Test2(Single d) {
Console::WriteLine("Base::Test2");
}
};
ref struct Derived : public Base {
void Test2(Double f) {
Console::WriteLine("Derived::Test2");
}
};
int main() {
Derived ^ t = gcnew Derived;
// Base::Test2 is a better match, but the compiler
// calls a function in the derived class if possible
t->Test2(3.14f);
}
輸出
Derived::Test2
下列範例顯示,即使基類具有與衍生類別相同的簽章,還是可以隱藏函式。
// compile with: /clr
using namespace System;
ref struct Base {
int Test4() {
Console::WriteLine("Base::Test4");
return 9;
}
};
ref struct Derived : public Base {
char Test4() {
Console::WriteLine("Derived::Test4");
return 'a';
}
};
int main() {
Derived ^ t = gcnew Derived;
// Base::Test4 is hidden
int i = t->Test4();
Console::WriteLine(i);
}
輸出
Derived::Test4
97
複製建構函式
C++ 標準表示,移動物件時會呼叫複製建構函式,如此一來,就會在同一個位址建立並終結物件。
不過,當編譯至 MSIL 的函式呼叫原生函式時,原生類別或多個類別會以值傳遞,而原生類別具有複製建構函式或解構函式,則不會呼叫任何複製建構函式,而且物件會終結在與其建立位置不同的位址。 如果類別本身有指標,或程式碼依位址追蹤物件,此行為可能會導致問題。
如需詳細資訊,請參閱 /clr (Common Language Runtime 編譯)。
下列範例示範何時不會產生複製建構函式。
// compile with: /clr
#include<stdio.h>
struct S {
int i;
static int n;
S() : i(n++) {
printf_s("S object %d being constructed, this=%p\n", i, this);
}
S(S const& rhs) : i(n++) {
printf_s("S object %d being copy constructed from S object "
"%d, this=%p\n", i, rhs.i, this);
}
~S() {
printf_s("S object %d being destroyed, this=%p\n", i, this);
}
};
int S::n = 0;
#pragma managed(push,off)
void f(S s1, S s2) {
printf_s("in function f\n");
}
#pragma managed(pop)
int main() {
S s;
S t;
f(s,t);
}
輸出
S object 0 being constructed, this=0018F378
S object 1 being constructed, this=0018F37C
S object 2 being copy constructed from S object 1, this=0018F380
S object 3 being copy constructed from S object 0, this=0018F384
S object 4 being copy constructed from S object 2, this=0018F2E4
S object 2 being destroyed, this=0018F380
S object 5 being copy constructed from S object 3, this=0018F2E0
S object 3 being destroyed, this=0018F384
in function f
S object 5 being destroyed, this=0018F2E0
S object 4 being destroyed, this=0018F2E4
S object 1 being destroyed, this=0018F37C
S object 0 being destroyed, this=0018F378
解構函式和完成項
參考型別中的解構函式會執行具決定性的資源清理。 完成項會清除 Unmanaged 資源,而且可由解構函式以決定性方式呼叫,或是由垃圾收集行程以非決定性方式呼叫。 如需標準 C++ 中解構函式的相關資訊,請參閱 解構函式 。
class classname {
~classname() {} // destructor
! classname() {} // finalizer
};
CLR 垃圾收集行程會刪除未使用的 Managed 物件,並在不再需要它們時釋放其記憶體。 不過,類型可能會使用垃圾收集行程不知道如何釋放的資源。 這些資源稱為 Unmanaged 資源(例如原生檔案控制代碼)。 建議您在完成項中釋放所有 Unmanaged 資源。 垃圾收集行程會以非決定性的方式釋放受控資源,因此在完成項中參考受控資源並不安全。 這是因為垃圾收集行程可能已經清除它們。
Visual C++ 完成項與 方法不同 Finalize 。 (CLR 檔使用完成項和 Finalize 方法同義)。 垃圾 Finalize 收集行程會呼叫 方法,它會叫用類別繼承鏈結中的每個完成項。 不同于 Visual C++ 解構函式,衍生類別完成項呼叫不會讓編譯器在所有基類中叫用完成項。
因為 Microsoft C++ 編譯器支援具決定性的資源版本,因此請勿嘗試實 Dispose 作 或 Finalize 方法。 不過,如果您熟悉這些方法,以下是 Visual C++ 完成項與呼叫完成項對應至模式的 Dispose 解構函式:
// Visual C++ code
ref class T {
~T() { this->!T(); } // destructor calls finalizer
!T() {} // finalizer
};
// equivalent to the Dispose pattern
void Dispose(bool disposing) {
if (disposing) {
~T();
} else {
!T();
}
}
受控類型也可以使用您偏好以決定性方式發行的受控資源。 您可能不希望垃圾收集行程在不再需要物件之後于某個時間點釋放物件。 資源的決定性釋放可以大幅改善效能。
Microsoft C++ 編譯器可讓解構函式的定義具決定性地清除物件。 使用解構函式來釋放您想要具決定性釋放的所有資源。 如果完成項存在,請從解構函式呼叫它,以避免程式碼重複。
// compile with: /clr /c
ref struct A {
// destructor cleans up all resources
~A() {
// clean up code to release managed resource
// ...
// to avoid code duplication,
// call finalizer to release unmanaged resources
this->!A();
}
// finalizer cleans up unmanaged resources
// destructor or garbage collector will
// clean up managed resources
!A() {
// clean up code to release unmanaged resources
// ...
}
};
如果取用您類型的程式碼不會呼叫解構函式,垃圾收集行程最終會釋放所有 Managed 資源。
解構函式的存在並不表示完成項的存在。 不過,完成項的存在表示您必須定義解構函式,並從該解構函式呼叫完成項。 此呼叫提供 Unmanaged 資源的決定性發行。
呼叫解構函式會使用 SuppressFinalize 物件的最終化來隱藏 。 如果未呼叫解構函式,則垃圾收集行程最終會呼叫您類型的完成項。
您可以藉由呼叫解構函式來確定性地清除物件的資源,而不是讓 CLR 不具決定性地完成物件,來改善效能。
以 Visual C++ 撰寫並使用 編譯的程式 /clr
代碼會在下列狀況下執行類型的解構函式:
使用堆疊語意建立的物件超出範圍。 如需詳細資訊,請參閱 參考類型的 C++ 堆疊語意。
物件建構期間擲回例外狀況。
物件是物件中的成員,其解構函式正在執行。
您在控制碼上呼叫 delete 運算子 ( Handle to Object Operator (^)]。
您明確呼叫解構函式。
如果以另一種語言撰寫的用戶端會取用您的類型,解構函式會呼叫如下:
Dispose呼叫 時。
在對 型別的呼叫
Dispose(void)
上。如果類型超出 C#
using
語句的範圍。
如果您未針對參考型別使用堆疊語意,並在 Managed 堆積上建立參考型別的物件,請使用 try-finally 語法來確保例外狀況不會防止解構函式執行。
// compile with: /clr
ref struct A {
~A() {}
};
int main() {
A ^ MyA = gcnew A;
try {
// use MyA
}
finally {
delete MyA;
}
}
如果您的類型具有解構函式,編譯器會產生實 Dispose
作 IDisposable 的方法。 如果以 Visual C++ 撰寫且具有從其他語言取用的解構函式,則呼叫 IDisposable::Dispose
該類型會導致呼叫該類型的解構函式。 從 Visual C++ 用戶端取用型別時,您無法直接呼叫 Dispose
;而是使用 delete
運算子呼叫解構函式。
如果您的類型具有完成項,編譯器會產生 Finalize(void)
覆寫 Finalize 的方法。
如果類型具有完成項或解構函式,則編譯器會 Dispose(bool)
根據設計模式產生方法。 (如需詳細資訊,請參閱 處置模式 )。 您無法在 Visual C++ 中明確撰寫或呼叫 Dispose(bool)
。
如果類型具有符合設計模式的基類,則會在呼叫衍生類別的解構函式時呼叫所有基類的解構函式。 (如果您的類型是以 Visual C++ 撰寫,編譯器可確保您的類型實作此模式。換句話說,參考類別的解構函式會鏈結至其基底和成員,如 C++ 標準所指定。 首先,會執行 類別的解構函式。 然後,其成員的解構函式會以建構的順序反轉執行。 最後,其基類的解構函式會以建構順序反向執行。
實值型別或介面內不允許解構函式和完成項。
完成項只能在參考型別中定義或宣告。 如同建構函式和解構函式,完成項沒有傳回類型。
在物件的完成項執行之後,也會呼叫任何基類中的完成項,開頭為最低衍生類型。 資料成員的完成項不會由類別的完成項自動鏈結至 。
如果完成項刪除 Managed 型別中的原生指標,您必須確定不會過早收集對 或透過原生指標的參考。 在 Managed 型別上呼叫解構函式,而不是使用 KeepAlive 。
在編譯時期,您可以偵測類型是否有完成項或解構函式。 如需詳細資訊,請參閱類型特徵的編譯器支援。
下一個範例顯示兩種類型:一種具有 Unmanaged 資源,另一種類型具有具決定性的受控資源。
// 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");
}