C++ 最强大的 .NET Framework编程语言

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;
    }
}
 
 
  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值