C++
:最强大的 .NET Framework
编程语言
本文涉及:
Microsoft Visual C++ 2005
Microsoft Visual C++ .NET
Microsoft Visual Studio 2005
通用语言运行时库(CLR)
文章概要:探索Visual C++ 2005中新语言C++/CLI的设计思想与基本原理,并以此 .NET编程语言,编写功能强大的 .NET应用程序。
内容:
简介
对象构造
内存管理 vs
资源管理
内存管理
资源管理
再论类型
装箱
编写引用和值类型
可访问性
属性
代理
结束语
简介
Visual C++开发小组花了大量的时间用于听取用户的意见,在对 .NET和C++经过仔细考量之后,决定在Visual C++ 2005中重新设计对通用语言运行时库(CLR)的支持,此项重新的设计被称为“C++/CLI”,它将为使用及编写CLR类型提供更自然的语法。在本文中,主要探讨了新的语法,并将之与C#和托管C++这两个CLR平台上极其相近的语言进行比较,在文中也会适当地以图表给出其与本地C++的相似之处。
通用语言运行时库(CLR)包括了一组规范,其是
Microsoft .NET
的基础,也是
CLI
的
Microsoft
版本实现。
C++/CLI
语言设计的目标是为了对
CLI
提供更自然的
C++
支持,而
Visual C++ 2005
的编译器则在
CLR
上实现了
C++/CLI
。
当在仔细研究了
Visual C++ 2005
编译器和
C++/CLI
语言设计之后,就会发现它们传达了两条重要的讯息;首先,
Visual C++
把自己定位于在
CLR
平台上的最低级编程语言,(看起来似乎没有必要使用其他语言了——包括
MSIL
);其次,
.NET
编程应与本地
C++
编程一样自然。
本文针对
C++
程序员,但并不想说服你放弃
C#
或者
Visual Basic .NET
。如果你非常喜欢
C++
,并想继续使用传统
C++
提供的全部功能,而又想要
C#
般的编程效率,那本文正适合你。另外,本文并不是
CLR
或
.NET Framework
的简介,而把重点放在
Visual C++ 2005
是怎样使你可以编写
.NET Framework
上更优雅和高效的代码。
对象构造
CLR
定义了两种类型:值类型和引用类型。值类型被设计用于可进行高效地分配和访问,它们与
C++
的内置类型大体相似,你也能创建属于你自己的类型,这就是
Bjarne Stroustrup
所称的具体类型;另一方面,引用类型被设计用于提供面向对象编程所需的特性,可用于创建有着层次结构的类:例如派生类和虚拟函数。另外在
CLR
中,引用类型自始至终都提供了额外的运行时特性,如自动内存管理——通常称为垃圾回收。同时,对引用类型和值类型,
CLR
也提供了精确的运行时类信息,这种特性通常被称为反射。
值类型分配在堆栈上;而引用类型通常分配在托管堆中——这是由
CLR
垃圾回收机制所管理的堆。如果你在
C++
中编写汇编代码,如平时那样,可在
CRT
堆中分配本地
C++
类型,在将来,
Visual C++
开发小组甚至允许你在托管堆中分配本地
C++
类型,毕竟,垃圾回收对本地类型来说,也是一个极具吸引力的主题。
本地
C++
允许选择在何处创建一个特定的对象,任何类型都可分配在堆栈或
CRT
堆中。
//
分配在堆栈上
std::wstring stackObject;
//
分配在
CRT
堆中
std::wstring* heapObject = new std::wstring;
如上所示,在何处分配对象是独立于类型的,主动权完全掌握在程序员的手中。另外,堆栈与堆的分配语法也是易于区别的。
另一方面,
C#
通常是在堆栈上创建值类型,而在托管堆中创建引用类型。下例中使用的
System.DateTime
类型,被声明为值类型。
//
分配在堆栈上
System.DateTime stackObject = new System.DateTime(2003, 1, 18);
//
分配在托管堆中
System.IO.MemoryStream heapObject = new System.IO.MemoryStream();
如上例所示,声明对象的方式并没有指出对象分配在堆栈上或托管堆中,其完全取决于程序编写者和运行时库。
C++
的托管扩展——简称为托管
C++
,可在本地
C++
代码中混合托管代码。为了遵循
C++
标准,
C++
被加入了扩展,以提供对
CLR
的全面支持。不幸的是,正是因为有太多的扩展,所以如果要用
C++
来编写大量的托管代码,就成了一件异常痛苦的事。
//
分配在堆栈上
DateTime stackObject(2003, 1, 18);
//
分配在托管堆中
IO::MemoryStream __gc* heapObject = __gc new IO::MemoryStream;
在
C++
程序员看来,在堆栈上分配一个值类型看起来非常正常,而在托管堆中的分配方式,看起来就有点怪:
__gc
是托管
C++
扩展中的一个关键字,有意思的是,在某些情况下,托管
C++
能推断你的意思,所以上述例子可重写为不带
__gc
关键字。
//
分配在托管堆中
IO::MemoryStream* heapObject = new IO::MemoryStream;
这样看起来更像本地
C++
代码了——但
heapObject
并不是一个真正的
C++
指针。
C++
程序员通常倾向于在指针中保存一个不变的数值,但垃圾回收器会在任何时候,在内存中移动对象。另一个不足之处是,不能仅仅依靠查看代码,就能确定对象是分配在本地还是托管堆中,必须知道程序编写者是怎样定义一个类型的。
C++/CLI
为此引入了句柄的概念,以便把
CLR
对象引用与
C++
指针区别开来。少了
C++
指针含义的重载,语言中也少了很多歧义,另外,通过句柄,对
CLR
也能提供更加自然的支持,例如,你能在
C++
中,直接对引用类型使用操作符重载,因为此时句柄已经能支持操作符重载了。由于
C++
禁止指针操作符重载,如果没有“托管”指针,这几乎不可能实现。
//
分配在堆栈上
DateTime stackObject(2003, 1, 18);
//
分配在托管堆中
IO::MemoryStream^ heapObject = gcnew IO::MemoryStream;
相对于值类型声明来说,和以前没什么不同,但对引用类型声明来说,变化却很明显,操作符
^
把变量声明为对一个
CLR
引用类型的句柄。当垃圾回收器在内存中移动被引用的对象时,同时也会自动更新句柄的值。另外,它们是可重绑定的,这允许它们可像
C++
指针那样指向不同的对象。另外需注意的一件事是,操作符
gcnew
已经代替了操作符
new
,清楚地指明了对象被分配在托管堆中。对托管类型,操作符
new
已经不能被重载(此处并非语带双关),只能把对象分配在
CRT
堆中,除非你提供自己重写的
new
操作符。
简而言之:本地
C++
指针已经与
CLR
对象引用大不相同了。
内存管理 vs
资源管理
当运行环境中包含垃圾回收机制时,区别开内存管理和资源管理,就非常重要了。典型地来说,垃圾回收器只对包含对象的内存之分配与释放感兴趣,它可不关心你的对象是否拥有其他的资源,如数据库连接或核心对象的句柄。
内存管理
本地
C++
为程序员提供了超越内存管理的直接控制能力,在堆栈上分配一个对象,意味着只有在进入特定函数时,才会为对象分配内存,而当函数返回或堆栈展开时,内存被释放。可使用操作符
new
来动态地为对象分配内存,此时内存分配在
CRT
堆中,并且需要程序员显存地对对象指针使用操作符
delete
,才能释放它。这种对内存的精确控制,也是
C++
可用于编写极度高效的程序的原因之一,但如果程序员不小心,这也是内存泄漏的原因。另一方面,你不需要求助于垃圾回收器来避免内存泄漏——实际上这是
CLR
所采取的方法,而且是一个非常有效的方法,当然,对于垃圾回收堆,也有其他一些好处,如改进的分配效率及引用位置相关的优势。所有这一切,都可以在
C++
中通过库支持来实现,但除此之处,
CLR
还提供了一个单一的内存管理编程模型,其对所有的编程语言都是通用的,想一想与
C++
中
COM
自动化对象相交互和调度数据类型所需做的一切工作,就会发现其重要意义所在——横跨数种编程语言的垃圾回收器,作用是非常巨大的。
为了效率,
CLR
也保留了堆栈的概念,以便值类型可在其上分配,但
CLR
也提供了一个
newobj
中间语言指令,以在托管堆中分配一个对象,但此指令只在
C#
中对引用对象使用操作符
new
时提供。在
CLR
中,没有与
C++
中的
delete
操作符对应的函数,当应用程序不再引用某对象时,分配的内存最后将由垃圾回收器回收。
当操作符
new
应用于引用类型时,托管
C++
也会生成
newobj
指令,当然,对此使用
delete
操作符是不合法的。这确实是一个矛盾,但同时也证明了为什么用
C++
指针概念来表示一个引用类型不是一个好的做法。
在内存管理方面,除了上述在对象构造一节讨论过的内容,
C++/CLI
没有提供任何新的东西;资源管理,才是
C++/CLI
的拿手好戏。
资源管理
CLR
只有在资源管理方面,才能胜过本地
C++
。
Bjarne Stroustrup
的“资源获取即初始化”的技术观点,基本定义了资源类型的模式,即类的构造函数获取资源,析构函数释放资源。这些类型是被当作堆栈上的局部对象,或复杂类型中的成员,其析构函数自动释放先前分配的资源。一如
Stroustrup
所言“对垃圾回收机制来说,
C++
是最好的语言,主要是因为它生成很少的垃圾。”
也许有一点令人惊讶,
CLR
并没有对资源管理提供任何显式运行时支持,
CLR
不支持类似析构函数的
C++
概念,而是在
.NET Framework
中,把资源管理这种模式,提升到一个
IDisposable
核心接口类型的中心位置。这种想法源自包装资源的类型,理应实现此接口的单一
Dispose
方法,以便调用者在不再使用资源时,可调用该方法。不必说,
C++
程序员会认为这是时代的倒退,因为他们习惯于编写那些缺省状态下清理就是正确的代码。
因为必须要调用一个方法来释放资源,由此带来的问题是,现在更难编写“全无异常”的代码了。因为异常随时都可能发生,你不可能只是简单地在一段代码后,放置一个对对象的
Dispose
方法的调用,这样做的话,就必须要冒资源泄漏的风险。在
C#
中解决这个问题的办法是,使用
try-finally
块和
using
语句,在面对异常时,可提供一个更可靠的办法来调用
Dispose
方法。有时,构造函数也会使用这种方法,但一般的情况是,你必须要记住手工编写它们,如果忘记了,生成的代码可能会存在一个悄无声息的错误。对缺乏真正析构函数的语言来说,是否需要
try-finally
块和
using
语句,还有待论证。
using (SqlConnection connection = new SqlConnection("Database=master; Integrated Security=sspi"))
{
SqlCommand command = connection.CreateCommand();
command.CommandText = "sp_databases";
command.CommandType = CommandType.StoredProcedure;
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader.GetString(0));
}
}
}
对托管
C++
来说,情节也非常类似,也需要使用一个
try-finally
语句,但其是
Microsoft
对
C++
的扩展。虽然很容易编写一个简单的
Using
模板类来包装
GCHandle
,并在模板类的析构函数中调用托管对象的
Dispose
方法,但托管
C++
中依然没有
C# using
语句的对等物。
Using<SqlConnection> connection(new SqlConnection(S"Database=master; Integrated Security=sspi"));
SqlCommand* command = connection->CreateCommand();
command->set_CommandText(S"sp_databases");
command->set_CommandType(CommandType::StoredProcedure);
connection->Open();
Using<SqlDataReader> reader(command->ExecuteReader());
while (reader->Read())
{
Console::WriteLine(reader->GetString(0));
}
想一下
C++
中对资源管理的传统支持,其对
C++/CLI
也是适用的,但
C++/CLI
的语言设计犹如为
C++
资源管理带来了一阵轻风。首先,在编写一个管理资源的类时,对大部分
CLR
平台语言来说,其中一个问题是怎样正确地实现
Dispose
模式,它可不像本地
C++
中经典的析构函数那样容易实现。当编写
Dispose
方法时,需要确定调用的是基类的
Dispose
方法——若有的话,另外,如果选择通过调用
Dispose
方法来实现类的
Finalize
方法,还必须关注并发访问,因为
Finalize
方法很可能被不同的线程所调用。此外,与正常程序代码相反,如果
Dispose
方法实际上是被
Finalize
方法调用的,还需要小心仔细地释放托管资源。
C++/CLI
并没有与上述情况脱离得太远,但它提供了许多帮助,在我们来看它提供了什么之前,先来快速回顾一下如今的
C#
和托管
C++
有多么接近。下例假设
Base
从
IDisposable
派生。
class Derived : Base
{
public override void Dispose()
{
try
{
//
释放托管与非托管资源
}
finally
{
base.Dispose();
}
}
~Derived() //
实现或重载
Object.Finalize
方法
{
//
只释放非托管资源
}
}
托管
C++
也与此类似,看起来像析构函数的代码其实是一个
Finalize
方法,编译器实际上插入了一个
try-finally
块并调用基类的
Finalize
方法,因此,
C#
与托管
C++
相对容易编写一个
Finalize
方法,但在编写
Dispose
方法时,却没有提供任何帮助。程序员们经常使用
Dispose
方法,把它当作一个伪析构函数以便在代码块末执行一点其他的代码,而不是为了释放任何资源。
C++/CLI
认识到了
Dispose
方法的重要性,并在引用类型中,使之成为一个逻辑“析构函数”。
ref class Derived : Base
{
~Derived() //
实现或重载
IDisposable::Dispose
方法
{
//
释放托管与非托管资源
}
!Derived() //
实现或重载
IDisposable::Dispose
方法
{
//
只释放非托管资源
}
};
对
C++
程序员来说,这让人感觉更自然了,能像以往那样,在析构函数中释放资源了。编译器会生成必要的
IL
(中间语言)来正确实现
IDisposable::Dispose
方法,包括抑制垃圾回收器调用对象的任何
Finalize
方法。事实上,在
C++/CLI
中,显式地实现
Dispose
方法是不合法的,而从
IDisposable
继承只会导致一个编译错误。当然,一旦类型通过编译,所有使用该类型的
CLI
语言,将只会看到
Dispose
模式以其每种语言最自然的方式得以实现。在
C#
中,可以直接调用
Dispose
方法,或使用一个
using
语句——如果类型定义在
C#
中。那么
C++
呢?难道要对堆中的对象正常地调用析构函数?此处当然是使用
delete
操作符了,对一个句柄使用
delete
操作符将会调用此对象的
Dispose
方法,而回收对象的内存是垃圾回收器该做的事,我们不需要关心释放那部分内存,只要释放对象的资源就行了。
Derived^ d = gcnew Derived();
d->SomeMethod()
delete d;
如果表达式中传递给
delete
操作符的是一个句柄,将会调用对象的
Dispose
方法,如果此时再没有其他对象链接到引用类型,垃圾回收器就会释放对象所占用的内存。如果表达式中是一个本地
C++
对象,在释放内存之前,还会调用对象的析构函数。
毫无疑问,在对象生命期管理上,我们越来越接近自然的
C++
语法,但要时刻记住使用
delete
操作符,却不是件易事。
C++/CLI
允许对引用类型使用堆栈语义,这意味着你能用在堆栈上分配对象的语法来使用一个引用类型,编译器会提供给你所期望的
C++
语义,而在底层,实际上仍是在托管堆中分配对象,以满足
CLR
的需要。
Derived d;
d.SomeMethod();
当
d
超出范围时,它的
Dispose
将会被调用,以释放它所占用的资源。再则,因为对象实际是在托管堆中分配的,所以垃圾回收器会在它的生命期结束时释放它。来看一个
ADO.NET
的例子,它与
C++/CLI
中的概念非常相似。
SqlConnection connection("Database=master; Integrated Security=sspi");
SqlCommand^ command = connection.CreateCommand();
command->CommandText = "sp_databases";
command->CommandType = CommandType::StoredProcedure;
connection.Open();
SqlDataReader reader(command->ExecuteReader());
while (reader.Read())
{
Console::WriteLine(reader.GetString(0));
}
再论类型
在讨论装箱(
boxing
)之前,有必要弄清楚为什么值类型与引用类型之间会有所区别。
一个含有数值的值类型的实例,和一个指向对象的引用类型的实例,它们有什么区别呢?除了存储对象所需的内存之外,每一个对象都会有一个对象头,目的是为面向对象的编程提供基本的服务,如存在虚方法的类,嵌入其中的元数据等等。由虚方法和接口间接结合的对象头,其内存开销通常会很大,哪怕你所需要的只是一个静态类型的数值,也会带来一些编译器的强制操作。有趣的是,在某些情况下,编译器能优化掉一些对象开销,但不总是能起作用。如果你非常在意托管代码的执行效率,那么使用数值或值类型将会有所益处,但在本地
C++
的类型中,这不算一个很大的区别,当然,
C++
也没有强制任何编程范式,所以也有可能在
C++
之上,通过创建库来建立一个这样截然不同的类型系统。
装箱
什么是装箱(
boxing
)?装箱是一种用来桥接数值和对象的机制。尽管
CLR
的每种类型都是直接或间接从
Object
类派生而来,但数值却不是。一个堆栈上的数值(如整形
int
),只不过是一个编译器会进行某种特定操作的内存块。如果你实在想把一个数值当成一个对象,必须对数值调用从
Object
继承而来的方法,为了实现这一点,
CLR
提供了装箱的概念。知道一点装箱的原理还是有点用的,首先,一个数值通过使用
ldloc IL
指令入栈,接下来,装箱
IL
指令运行,把数值类型提升,
CLR
再把数值出栈,并分配足够的空间存储数值与对象头,然后一个对新建对象的引用被压入栈,所有这些就是装箱指令要做的事。最后,为取得对象引用,
stloc IL
指令从堆栈中弹出引用,并把它存储在局部变量中。
现在,问题是:在编程语言中,对数值的装箱操作,是应该表现为隐式还是显式呢?换句话说,是否应使用一个显式转换或其他构造函数呢?
C#
语言设计者决定做成隐式转换,毕竟,一个整形数是从
Object
间接继承来的
Int32
类型。
int i = 123;
object o = i;
问题来了,正如我们所知,装箱不是一个简单的向上转换,它有点像把一个数值转换成一个对象,是一个存在潜在代价的操作。正是因为这个原因,托管
C++
通过使用关键字
__box
,来进行显式装箱。
int i = 123;
Object* o = __box(i);
当然,在托管
C++
中,当装箱一个数值时,不会失去静态类型信息,而这一点,正是
C#
所缺乏的。
int i = 123;
int __gc* o = __box(i);
指定强类型的装箱值有利于再次转换回到一个数值类型,或被称为解箱(
unboxing
),不使用
dynamic_cast
,只是简单地解引用一个对象。
int c = *o;
当然,托管
C++
的显式装箱所带来的句法上的花销,在许多情况下已被证明是巨大的。正因为此,改变了
C++/CLI
语言的设计过程,成了与
C#
保持一致——隐式装箱。在相同情况下,它在直接表示强类型装箱数值上保持了类型安全,而这正是其他
.NET
语言所做不到的。
int i = 123;
int^ hi = i;
int c = *hi;
hi = nullptr;
在此,也暗示着一个没有指向任何对象的句柄,不能被初始化为零,在这一点上,与指针是一致的,因为这将导致只是把数值“零”装箱;同时这也是常量
nullptr
存在的原因,它能被赋给任何句柄,且是
C#
中关键字
null
的对等物。尽管在
C++/CLI
语言设计中,
nullptr
是一个新的保留字,但它已被
Herb Sutter
和
Bjarne Stroustrup
提议增加在标准
C++
中。
编写引用和值类型
在
C#
中,通常用关键字
class
来声明一个引用类型,而用关键字
struct
来声明值类型:
class ReferenceType {}
struct ValueType {}
对于
class
和
struct
,
C++
已经有定义好了的含义,所以这在
C++
中行不通。在最初的语言设计上,放置在类前的关键字
__gc
表示这是一个引用类型,而关键字
__value
则表示这是一个值类型。
__gc class ReferenceType {};
__value class ValueType {};
C++/CLI
在那些不会与用户的其他标识符发生冲突的地方引入了“空隔”关键字。为了声明一个引用类型,只需在
class
或
struct
之前加上
ref
,类似地,可用
value
来声明值类型。
ref class ReferenceType {};
ref struct ReferenceType {};
value class ValueType {};
value struct ValueType {};
关于使用
class
还是
struct
,与默认状态下类成员的可见度有关,在
CLR
中,最大的不同之处在于,只支持公有继承。使用
private
(私有)或
protected
(保护)继承都将会导致编译错误,因此,显式声明公有继承是合法但却多余的。
可访问性
CLR
定义了一些用于访问存取的修饰成分,其作用超越了本地
C++
中类成员函数与变量的对等物(如:
public
、
private
、
protected
),不仅如此,甚至还能定义命名空间或嵌套类型的可访问性。为了让
C++/CLI
达到作为低级语言的目标,除访问性之外,它还提供了比其他
CLR
平台高级语言更多的控制。
本地
C++
可访问性与
CLR
中定义的可访问性相比,其最大不同之处在于:本地
C++
访问指示符通常用于限制同一程序中从其他代码访问类成员;而
CLR
定义的类型和成员的可访问性,不只是针对同一程序集中的其他代码,还针对从其他程序集中引用它的代码。
一个命名空间,或非嵌套类型,如
class
或
delegate
类型,可在类型定义之前,通过加上
public
或
private
关键字来指定程序集之外的可见度。
public ref class ReferenceType {};
如果显式地指定了可见度,对程序集来说,类型会被假定为私有类型(
private
)。
类成员的访问指示符同样也被扩展,以允许一起使用两个关键字来指定来自内部和外部的访问。在这两个关键字中,限制更多的一者定义了来自程序集外的访问性,而另外一个则定义了程序集内的访问性。如果只用了一个关键字,那它将同时作用于内部与外部的访问性。这种设计思想对定义类型与类成员的可访问性提供了巨大的弹性,以下是示例:
public ref class ReferenceType
{
public:
//
程序集内部与外部均可见
private public:
//
只对程序集内部可见
protected public:
//
对程序集内所有代码可见;对外部继承类型可见
};
属性
除嵌套类型之外,
CLR
类型只能包含方法与字段。为了让程序员更清楚地表达代码内涵,可使用元数据来指明某特定的方法将被编程语言当作属性。严格来说,一个
CLR
属性是它的包含类的一个成员,然而,属性没有分配的存储空间,它只是实现属性的各自方法的一个命名引用,而不同的编译器,碰到源代码中有关属性的语法时,将会生成各自所需的元数据。这就是说,类型的使用者,可在它们的语言中使用属性语法,来访问实现属性的
get
和
set
方法。与本地
C++
相比,
C#
对属性支持最好。
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
C#
编译器会生成对应的
get_Name
与
set_Name
方法,并且也会包含必要的元数据以指明其联系。在托管
C++
中,引入了关键字
__property
来指明一个在语义上实现属性的方法。
__property String* get_Name()
{
return m_value;
}
__property String* set_Name(String* value)
{
m_value = value;
}
很明显,这不是理想的情况,不但需要使用这个“难看”的
__property
关键字,而且此处没有任何东西清楚地指明这两个成员函数实际上的联系,这在维护期间,会导致难以捉摸的
bug
。
C++/CLI
在对属性的设计上,就显得简明多了,更接近于
C#
的设计,而且你还会发现,这更强大。
property String^ Name
{
String^ get()
{
return m_value;
}
void set(String^ value)
{
m_value = value;
}
}
这是一个非常大的改进,由编译器负责生成
get_Name
与
set_Name
方法和在此属性中声明的必要的元数据。更好的是,此属性值可对程序集外部保持只读,而对程序集内部为可写,也可以在紧接着属性名的花括号中使用访问指示符来达到这一目的。
property String^ Name
{
public:
String^ get();
private public:
void set(String^);
}
最后一点无关痛痒的事是,在那此不需要对属性中
get
和
set
作特殊处理的地方,也可以使用简略表示法。
property String^ Name;
再次提醒,编译器会生成
get_Name
与
set_Name
方法,但是这个时候,也会提供一个由
private String ^
成员变量支持的默认实现。其好处是,你可在将来某个时刻,用其他某种实现,来替换掉此处的简易属性,并且不会破坏类的接口。
代理
本地
C++
中的函数指针,提供了一种异步执行代码的机制,你可以存储一个函数指针,而在以后有需要的时候及时地调用,这通常用于把某算法与实现代码分开来,如在搜索中比较对象。另外,它也可在不同的线程中调用,以实现真实的异步编程。以下是一个
ThreadPool
类的简单示例,允许你排列一系列的函数指针,并在工作者线程中执行。
class ThreadPool
{
public:
template <typename T>
static void QueueUserWorkItem(void (T::*function)(), T* object)
{
typedef std::pair<void (T::*)(), T*> CallbackType;
std::auto_ptr<CallbackType> p(new CallbackType(function, object));
if (::QueueUserWorkItem(ThreadProc<T>,
p.get(),
WT_EXECUTEDEFAULT))
{
//ThreadProc
负责删除
pair.
p.release();
}
else
{
AtlThrowLastWin32();
}
}
private:
template <typename T>
static DWORD WINAPI ThreadProc(PVOID context)
{
typedef std::pair<void (T::*)(), T*> CallbackType;
std::auto_ptr<CallbackType> p(static_cast<CallbackType*>(context));
(p->second->*p->first)();
return 0;
}
ThreadPool();
};
在
C++
中使用线程池是简单兼自然的。
class Service
{
public:
void AsyncRun()
{
ThreadPool::QueueUserWorkItem(Run, this);
}
void Run()
{
//
其他代码
}
}
很明显,
ThreadPool
类是非常受限的,它只能接受特定的函数指针,这只是示例本身而不是
C++
本身的局限性。
当
C++
程序员想要实现或得到用于异步编程的丰富库函数时,带着有此内置支持的
CLR
来了。“代理”与函数指针非常类似,除了针对的目标及方法属于的类型(其不能决定是否一个代理可以绑定于一个给定的方法);只要类型匹配,方法就能被代理,并在以后调用,与上面使用
C++
模板来实现允许接受任何类成员函数的例子比较,这在思想上是相似的。当然,代理还提供了更多且极其有用的间接函数调用的机制,以下是在
C++/CLI
中定义一个代理类型的示例:
delegate void Function();
使用代理也非常直截了当。
ref struct ReferenceType
{
void InstanceMethod() {}
static void StaticMethod() {}
};
//
创建代理并绑定到成员函数的实例
Function^ f = gcnew Function(gcnew ReferenceType, ReferenceType::InstanceMethod);
//
也可绑定到静态成员函数,并结合几个代理形成代理链
f += gcnew Function(ReferenceType::StaticMethod);
//
调用函数
f();
结束语
关于
C++/CLI
,真是说上几天也说不完,新的语言设计提供了空前的威力和绝无仅有的“优雅”语法,并且可在不牺牲简洁性、编程效率、执行效率的情况下,完全地使用
C++
来编写丰富的
.NET
应用程序。
下表概要说明了常用的类型,以作快速参考。
描述
|
C++/CLI
|
C#
|
分配引用类型
|
ReferenceType^ h = gcnew ReferenceType;
|
ReferenceType h = new ReferenceType();
|
分配值类型
|
ValueType v(3, 4);
|
ValueType v = new ValueType(3, 4);
|
堆栈语法形式的引用类型
|
ReferenceType h;
|
N/A
|
调用
Dispose
方法
|
ReferenceType^ h = gcnew ReferenceType;
delete h;
|
ReferenceType h = new ReferenceType();
((IDisposable)h).Dispose();
|
实现
Dispose
方法
|
~TypeName() {}
|
void IDisposable.Dispose() {}
|
实现
Finalize
方法
|
!TypeName() {}
|
~TypeName() {}
|
装箱
|
int^ h = 123;
|
object h = 123;
|
解箱
|
int^ hi = 123;
int c = *hi;
|
object h = 123;
int i = (int) h;
|
定义引用类型
|
ref class ReferenceType {};
ref struct ReferenceType {};
|
class ReferenceType {}
|
定义值类型
|
value class ValueType {};
value struct ValueType {};
|
struct ValueType {}
|
使用属性
|
h.Prop = 123;
int v = h.Prop;
|
h.Prop = 123;
int v = h.Prop;
|
定义属性
|
property String^ Name { String^ get() { return m_value; } void set(String^ value) { m_value = value; } } |
string Name { get { return m_name; } set { m_name = value; } } |