如何:定义和使用类和结构 (C++/CLI)
本文演示如何定义和使用用户定义引用类型,而值类型 C++/CLI。
对象实例化
引用 (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。
默认情况下在 Visual C++ 2005 之前,本机类型具有程序集外公共可访问性。 使 编译器警告(等级 1)C4692 帮助您发现位置不正确地使用私有本机类型。 使用 make_public 说明为公共可访问性本机类型不能修改的源代码文件。
有关更多信息,请参见#using指令(C++)。
下面的示例演示如何声明类型并指定它们的可访问性,然后访问在程序集中的某些类型。 当然,使用 #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();
}
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可以减少对公共选件类成员的访问从同一程序集的内部变量
下表汇总了各种访问说明符的效果:
说明符 |
效果 |
---|---|
|
成员可访问的内部和外部程序集。 有关更多信息,请参见公共(C++)。 |
|
成员不可访问,以及在程序集外。 有关更多信息,请参见私有(C++)。 |
|
成员是可访问的于并且仅对外部程序集,但是,自派生类型。 有关更多信息,请参见保护(C++)。 |
|
成员在程序集外是公共在程序集内,但私有的。 internal 是上下文相关关键字。 有关更多信息,请参见上下文相关的关键字(C++ 组件扩展)。 |
|
成员是公共在程序集内,但在程序集外保护。 |
|
保护成员在程序集内,但在私有程序集中。 |
下面的示例显示具有成员声明了不同的可访问性,然后显示访问这些成员从程序集内的公共类型。
// 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
公共和私有本机选件类
本机类型可以从托管类型引用。 例如,在托管类型的函数会采用类型是本机结构的参数。 如果托管和函数的类型是公共的程序集中,则本机类型也必须是公共的。
// 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,仅调用定义静态构造函数作为私有成员函数。
有关静态构造函数的更多信息,请参见 如何:定义接口静态构造函数 (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
此指针的语义
当使用 Visual C++ 定义类型时,在引用类型的 this 指针为类型“handles”。 在值类型的 this 指针为类型“内部指针”。
尽管默认值索引器调用时,这些 this 指针的语义不同可能导致意外行为。 下一个示例显示正确方法访问一个默认值索引器在 ref 类型和值类型。
有关更多信息,请参见
// 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
隐藏由签名功能
在标准 C++ 中,在基类中的函数由具有相同名称在派生类中隐藏功能,因此,即使派生类功能具有相同数量或参数。 这称为" 按名称隐藏 语义。 在引用类型,因此,如果该名称和参数列表相同,在基类中的函数可以按功能仅隐藏在派生类。 这称为" 隐藏由签名 语义。
当其所有函数在元数据标记为 hidebysig时,选件类被视为隐藏由签名选件类。 默认情况下,创建在 /clr 下的所有选件类具有 hidebysig 功能。 但是,编译使用 /clr:oldSyntax 的选件类没有 hidebysig 功能;相反,它们是隐藏按名排序功能。 当选件类具有 hidebysig 功能时,编译器将任何不按名称隐藏功能直接基类,但是,如果编译器遇到在继承链中隐藏按名称选件类,将继续该隐藏按名排序行为。
隐藏由签名语义下,那么,当函数调用对象时,编译器确定包含一个功能可以满足函数调用的派生类。 如果只有在无法满足调用的选件类中的函数,编译器调用该函数。 如果存在多个可以满足调用的选件类中的函数,编译器使用 hyper-v 加载决策规则确定要调用的函数。 有关超加载规则的更多信息,请参见 函数重载、"。
对于特定函数调用,在基类中的函数可能具有与在派生类中的函数使其成为稍微好的匹配的签名。 但是,在中,如果函数显式调用该派生类的对象,派生类中的函数调用。
由于返回值不被视为一部分的函数的签名,一个基类功能隐藏,如果它具有相同名称和取出操作的数量和种类参数和一个派生类功能相同,因此,即使在返回值的类型不同。
下面的示例显示,在基类中的函数不受功能隐藏在派生类。
// 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,生成的一个元素。 定义使用 C++ 托管扩展的选件类具有隐藏按名成员函数。
// 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");
}
};
下一个示例使用上一示例中生成的组件。 通知隐藏由签名功能如何不会应用于通过使用 /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++ 选件类的使用 C++ 托管扩展不同。 有关此更改的更多信息,请参见析构函数语义的更改。
在不再需要时,CLR 垃圾回收器删除未使用的托管对象并释放其内存中。 但是,类型可以使用垃圾回收器不会释放的资源。 这些资源称为非托管资源 (如本机文件句柄,)。 建议您将释放终结器的所有非托管资源。 由于垃圾回收器释放托管资源以非确定性的方式,指终结器托管资源是不安全的,因为是可能的垃圾回收器已清理了该托管资源。
Visual C++ 终结器与 Finalize 方法。 (CLR 文档使用终结器和 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 运行类型的析构函数,则:
使用创建堆栈语义的对象超出范围。 有关更多信息,请参见C++堆栈语义对于引用类型。
异常在构造对象时引发。
对象是析构函数运行的对象的成员。
在调用处理 (对象句柄运算符 (^)(C++ 组件扩展)) 删除 运算符。
显式调用析构函数。
如果您的类型由以另一种语言编写的客户端使用,调用析构函数如下所示:
在对 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;
}
}
如果您的类型具有析构函数,编译器会生成一个 Dispose 的方法实现 IDisposable。 如果在 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。
在编译时,可以检测类型是否具有终结器或一个析构函数。 有关更多信息,请参见编译器支持类型特征(C++ 组件扩展)。
下面的示例演示两个类型,具有非托管资源托管资源确定性发布的一个和一个。
// 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");
}