事情中的C++:根基道理,重点推荐和竣事语
副标题#e#
目次
根基道理
丢弃托管扩展
将来的兴趣
编程的演变
终极方针
竣事语,尽量不是再见
这个月,我要改变通例的问答形式来汇报您关于我在网上发明的一个很是不错的文档。几个礼拜前,有人写信询问我,为什么他不能在 C++/CLI 中声明 const 函数:
// 引用类
ref class A {
void f() const; // 不!
};
对此,我回覆道:您就是不能,这是法则。民众语言基本布局 (CLI) 是为支持诸如 Visual Basic®、Java 甚至是 COBOL 语言而设计的 – 这些语言甚至不知道 const 的寄义。因为 CLI 不知道 const 成员函数为何物,所以您不能利用。
我打完回覆后,恍惚地记起了一些埋藏在影象深处的一些对象,关于 const,关于如那里理惩罚编译器提示以使其他语言可以忽略这些提示。我查找了以前的专栏,发明曾在 2004 年 7 月答复过一个关于 const 的问题。实际上,C++/CLI 简直答允声明 const 数据成员和参数 – 但不是 const 成员函数。图 1 显示了一个小措施,其具有 const 静态数据成员的引用类。假如编译此措施,然后利用 ILDASM 来反汇编,您将看到雷同于下面的信息:
field public static int32
modopt([mscorlib]System.Runtime.CompilerServices.IsConst)
g_private = int32(0x00000001)
Figure1const.cpp
////////////////////////////////////////////////////////////////
// 要编译范例:
// cl /clr const.cpp
//
#include <stdio.h>
ref class A {
int m_val;
// 答允利用常量数据成员,将生成 modopt
static const int g_private = 1;
public:
// 民众常量成员可由 Visual Basic 或其他不能识别常量的措施
// 来修改(因而改用字面量)。
literal int g_public = 1;
A(int i) { m_val = i; }
void print(); // const; // 不!不答允利用常量 fn
};
void A::print()
{
printf("Val is %d\n",m_val);
}
int main()
{
A a(17);
a.print();
}
Modopt(可选的修饰符)是一种对 CLI 利用者宣告的 MSIL 声明符:假如您相识它,很好;假如不相识,可以完全忽略它。相反,modreq(所需的修饰符)暗示:假如您不相识它,就不能利用此函数。Volatile 即是 modreq 的一个示例。因为一个 volatile 引用随时都可以被操纵系统/硬件(甚至是另一个线程)变动,所以 CLI 利用者最好知道 volatile 的寄义,假如想要利用 volatile 工具的话。但 const 是可选的。请留意,尽量托管扩展会将 C++ const 工具转化成 CLI 文字量,C++/CLI 不再这么做了。因此要小心 – 假如声明一个民众 const 数据成员,用像 Visual Basic 这样的语言编写的客户端可以或许改变它的值。假如您但愿 CLI 客户端不能改变该值,就应该声明工具字面量,如在图 1 中所示。可是,成员函数呢 – 为什么 const 成员函数是不答允的?
#p#副标题#e#
根基道理
噢,我在查找回覆的进程中,偶尔发明白一篇很好的文章,称为《A Design Rationale for C++/CLI》(C++/CLI 设计根基道理),出自 Microsoft 的 Herb Sutter 之手。Herb 是 C++/CLI 的架构师之一。您可以从 Herb 的博客上得到我在后头称作“根基道理”的文档(URL 长短常长的字符串 – 只要在 Web 上搜索“C++/CLI Rationale”就会找到)。正如标题表白的那样,“根基道理”表明白 C++/CLI 中所有事物为何故那种方法存在。它答复了从“为什么首先扩展 C++”到我本身关于 const 函数疑问的所有问题。我认为,每个想要相识 C++/CLI 深层道理的人都有须要阅读“根基道理”。由于“根基道理”中有许多反复,因此我想给出一些重点推荐。
就让我们从最重要的问题开始吧,为什么首先扩展 C++?简朴明白的答复就是:要使 C++ 成为一等 CLI 国民。Microsoft® .NET Framework 是 Windows® 开拓的将来。哦,连 COBOL 都支持 CLI。所以,C++/CLI 旨在于确保 C++ 的不绝乐成。
但为什么弄乱 C++?为什么要用像 ^ 和 % 这样讨厌的新观念和像 ref、value 和 property 这样的新要害字来扩展语言呢?哦,是因为没有其他步伐了。关于此内容,“根基道理”引用的不是此外,正是 Bjarne Stroustrup 写的:“类险些可以暗示我们所需的所有观念。只有库的阶梯确实不行行时,才应该走语言扩展的阶梯。”
对付普通的 C++ 代码,把 CLI 作为方针就像是编写一个针对差异处理惩罚器的编译器:没有问题。可是,CLI 引入了需要非凡代码生成且不能在 C++ 中表达的新观念。譬喻,属性需要非凡的元数据。无法用库或模板实现属性。“根基道理”接头了一些为属性(和其他 CLI 成果)思量的替代的语法,以及它们被拒绝的原因。
丢弃托管扩展
#p#分页标题#e#
“根基道理”还表明白为什么 Microsoft 抉择丢弃托管扩展。托管扩展为托管工具和本机工具均利用 * 是一个智慧勇敢的实验,将 C++ 和 CLI 统一,而不改变语言,但这样使引用和本机工具之间的重要区别不明明晰。这两种指针看起来一样,但行为差异。譬喻,析构语义、复制结构函数和结构函数/析构函数中的虚拟挪用都按照指针实际指向的工具种类而具有差异的行为。太糟了!正如“根基道理”所述,“不单要埋没不须要的区别,并且要显露本质的区别,这很重要”,再次引用 Bjarne 说过的:“我试着使重要操纵高度可见”。本机和托管类是基础差异的规模,因此最好将区别突出,而不是掩盖。C++/CLI 团队思量并否认了各类机制,最终抉择是 gcnew ^(句柄)和 %(跟踪引用)。像这样区分托管和本机类具有意想不到的附加收益。譬喻,利用单独的 gcnew 运算符来分派托管工具开启了某一天本机类可以从托管堆分派的大概性 – 反之亦然!
您有没有想过,为什么托管扩展具有那些讨厌的下划线要害字(像 __gc 和 __value)?因为托管扩展严格遵循 C++ 尺度,尺度提到“假如你确实必需引入新的要害字,就应该将其定名为从双下划线开始!”猜猜会产生什么?Microsoft 引入了 __gc、__value 和其他的要害字后,Redmondtonians 遭到了来自措施员们“出乎料想强烈”的诉苦。是的!全世界的措施员们都连系起来了!只有放弃下划线了。下划线使代码看起来很讨厌,就像某种汇编语言措施或此外什么。所以 C++/CLI 有 ref 和 value,没有下划线。这意味着向 C++ 添加新的要害字,但那又怎么样呢?就像 Bjarne 说的,“我的履历是人们热衷于引入观念的要害字,自己不具有要害字的观念很难教学。这个浸染比人们口头上表达的对新的要害字不喜欢来说更重要,更根深蒂固。”(确实如此。我喜欢 Bjarne 描写编程心理学。)所以 C++/CLI 丢弃了下划线。通过使其成为位置要害字,而不是保存的要害字,它们就不会与大概已经用这些字作为变量或函数名称的措施相斗嘴了。
好奇的读者大概想知道:在丢弃其下划线的进程中,gc 怎么就酿成 ref 了呢?正如“根基道理”表明的那样,关于托管类,重要之处不是它们存在于托管(垃圾收集)堆上。重要之处是它们的引用语义。句柄 (^) 起到雷同于引用的浸染,而不是指针。大白了吗?读过“根基道理”之后,一切自然明白。
假如显露本质的区别很重要,那么埋没外貌的区别也同样重要。譬喻,无论工具是本机、引用照旧值,所有运算符重载都凭据任何一个 C++ 措施员城市期望的方法“正常运行”。假如需要 C++/CLI 掩盖区此外其他例子,请看下面的摘录:
// 引用类 R 作为当地变量
void f()
{
R r; // 引用类位于栈上?
r.DoSomething();
...
}
在这里,r 看起来像是一个栈工具,但就连 Mopsie 大婶都知道托管类不能在栈长举办物理分派;它们必需从托管堆中分派。那么?编译器可以使这个摘录片段凭据期望的方法运行,要领是生成和下面一样的内容:
// 编译器如何才气看到它。
void f()
{
R^ r = gcnew R; // 在 gc 堆上分派
try
{
r->DoSomething();
...
}
finally
{
delete r;
}
}
工具不存在于物理栈上,但谁又介怀呢?重要的是局部变量语法凭据任何一个 C++ 措施员城市期望的方法运行。出格是,r 的析构函数在分开 f 前被挪用。而说到析构函数,同理表明白 C++/CLI 规复抉择性析构的原因:这样,析构就遵循了每个 C++ 措施员都熟悉并喜爱的沟通语义。好啊!非抉择性析构是托管扩展最疾苦的工作之一。C++/CLI 将析构函数映射到 Dispose 并为终结器引入非凡的 ! 语法。直到垃圾收集器抽出时间时,引用工具利用的内存才会被收回,但这没什么大不了。C++ 措施员不怎么在乎一个工具被销毁时收回内存;在乎的是析构函数可以在期望运行时运行。C++ 措施员常常利用结构/析构模式来初始化/释放非内存资源(像文件句柄、数据库锁等)。利用 C++/CLI,对付引用和本机类,熟悉的结构/析构模式就会像您预期的那样运行。Redmondtonians 值得信任,因为他率直认可托管扩展存在的问题 – 还因为他处理惩罚了这些问题。
将来的兴趣
#p#分页标题#e#
在“根基道理”更吸引人的章节中,有一个称为“Future Unifications”(将来统一化)的章节。它给出了一些关于 C++/CLI 将来走势的引人入胜的提示。譬喻,本机类当前无法从托管类派生,反之亦然。但可以得到同样的结果,办理步伐是:将“基”类作为数据成员,然后编写 passthrough 包装措施,该包装措施只起到挪用包括的实例的浸染。哦,这个听起来挺老套,那么为什么编译器做不到呢?编译器可以将殽杂工具作为分成两部门的工具暗示,个中一个是包括所有 CLI 部门的 CLI 工具,另一个是包括所有 C++ 部门的 C++ 工具。
在这里,Sutter 报道了一个有趣的轶事:当他第一次向 Bjarne Stroustrup 表白这个殽杂类的主意时,Bjarne 走向书架“然后打开一本书,个中写道他一贯僵持(不管品评)C++ 不须要求工具在一个单独的内存块中持续分列。”当时,没有人看到非持续工具有任何长处 – 但当时也没有谁预推测 .NET 和 CLI。Bjarne 僵持敞开非持续的大门使得殽杂工具成为大概。假如在将来的 C++/CLI 版本中看到它们,不要惊奇。而寓意就是:当您正在设计一种期望会永久存在并会以不行估量的方法成长的新的语言或巨大的措施时,请不要做不须要的设想,因为这样会让糊口更轻松!
“根基道理”提供了另一则有趣的汗青珍闻:Redmondtonians 本来为 C++/CLI 取的内部名称是 MC^2。正如在 M(托管)C(++) 中,有一个致与 Albert Einstein 的帽尖 (^)。但正如 Sutter 所说的,那样“太装腔作势”。我同意。您真的认为假如它被称作 MC^2,各人就会热烈接管 C++/CLI 吗?在抉择将其称为 C++/CLI 方面,架构师再一次沿着 Bjarne 的足迹。Bjarne 说:“我选择了 C++,因为它简短、有很好的释义,并且它不是‘修饰的 C’的形式”。C++/CLI 表白 C++ 在前,并且还审慎地制止了“修饰的 C++”的形式。
“根基道理”证明白其他的 C++/CLI 扩展(像 property、gcnew、generic 和 const)是有效的,并以很有用的常见问题解答部门竣事。有关具体信息,请下载“根基道理”自行阅读。说到 const,回到我最初的问题 – 为什么 C++/CLI 答允 const 数据,但不答允 const 函数呢?简短的答复就是:CLI 不答允您将 modopt/modreq 直接放在函数上(尽量 CLI 实际上确实有步伐在元数据中编码此信息,只是未测试罢了)。至少此刻还不可,“根基道理”叙述得很小心,体现大概某一天会加上这个成果。
编程的演变
C++/CLI 使 C++ 成为了一等 CLI 国民。另外,假如您阅读了“根基道理”,就会心识到做到这一点只对 C++ 作了最少的窜改。并且 C++ 仍然是系统编程最好的语言,因为其相对付任何其他语言,提供了更多对 CLI 的直接会见,还因为您假如想要挪用旧的 Win32® API(将会伴随我们更长的时间,我必定),仍可以降回到 C。
要领略 C++/CLI 有何等重要以及它代表了什么,就必需思量我们处于编程演变进程中的什么位置。就让我给您一个简捷、出格而快速的回首吧。在已往,措施员利用切换开关来编写措施。纸带是一个改造,但每台计较机都有其自身的“呆板”语言。跟着计较机的成长壮大,措施员不得不为每台新的呆板都从头编写其措施。唉,那长短常烦的,因此措施员发现了像 FORTRAN、BASIC 和 C 这样的高级语言,利用了称为“编译器”的对象来将高级语言翻译为针对每台呆板的呆板指令。图 2 说明白这点。此刻,可以编写一次措施,然后针对差异的呆板举办编译。太酷了!C 语言成为系统编程的选择,因为它是“最初级的高级语言”,这意味着它在自身和呆板之间引入了最少的累赘。此刻利用的大大都操纵系统都是用 C 语言编写的,在少数对机能要求出格严格或与硬件交互的部门大概回收汇编语言举办编码。
很多年今后,C++ 改造了 C 语言,使其面向工具,哦,并且更有趣。再次引用 Bjarne 的话:“C++ 的设计旨在于使作者和他的伴侣不必用汇编语言、C 语言或各类现代高级语言来编程。它的主要目标就是使单个措施员更轻松愉快地编写好的措施。”C++ 很棒,可是高级语言相互之间的通讯不太好。假如您用 C++ 编写了某些代码,那么不能在 BASIC 中利用它 – 反过来也一样,至少很是坚苦。每种语言都在其本身的世界中运行。对付独立的应用措施来说,这很好,可是跟着应用措施变得越发巨大和情况越发分手,共享代码的需求就变得越发急切。从第一个子措施开始,措施员就在寻求完全封装的可重用组件的终极方针:措施员可通过组装软件小结构块来建设应用措施。那么为什么所有部门都得用沟通的语言编写呢?
#p#分页标题#e#
多年来,形成了各类百般办理互操纵组件问题的步伐。起初,语言与库一起提供(想想 C 运行库和 printf)。在 Windows 规模内,DLL 提供了延时加载(DLL 中的动态)来节省内存。DLL 也提供了互操纵性,因为像 Visual Basic 和 COBOL 这样的语言可以挪用 DLL,要领是通过引入指引编译器将正确的 C 链接挪用发到 DLL 中的导入语句。可是应用措施和 DLL 之间的链接太细密、太懦弱并且太容易间断。每个应用措施都要知道 DLL 中每个进口的名称和签名。别的,反向挪用(从 DLL 到应用措施)也很讨厌,因为您必需通报函数指针作为回调。因此,措施员发现了 VBX,厥后成为了 OCX,再厥后成为了 COM。很兴奋,COM 与语言无关:它具有“范例库”,所以语言不必在链接时知道函数;它们可以在运行时查询范例库。COM 很是酷,但众所周知,很难编写。(嘲讽地是,用 C++ 编写 COM 是最坚苦的,而 COM 正是利用这种语言构想和实现的!)COM 也有其他问题:它太初级了,并且不处理惩罚安详性或内存打点这样的工作。
终极方针
此刻是 2007 年,我们拥有 .NET Framework 及其尺度子集 CLI。通过在编程语言和呆板之间插入一个新的抽象层,CLI 以一种截然差异的方法办理了复用性问题。编译器此刻生成的是 MSIL 代码,而不是呆板指令,然后 CLI 虚拟机/及时 (JIT) 编译器将代码动态编译为呆板代码。虚拟机(VES 或虚拟执行系统,对付那些喜欢缩写词的人)提供了一个位于呆板之上的抽象层。虚拟机不是新的。事实上,它们已经存在了很长时间。像 Pascal 和 ZIL(Zork Implementation Language,在 Infocom 内部用于编写游戏,我曾在 Infocom 事情过)这样的语言,通过将高级措施编译为 P 代码(或 Z 代码)来运行,随后由虚拟机举办表明。可是,CLI 提供了一个所有语言都可以利用的民众虚拟机(CLI 中的 C)。CLI 支持像类、属性、担任、反射等的根基观念。VES/VM 提供了像内存打点和安详性这样的成果,因此措施员不必担忧缓冲区溢出,可能其它大概为恶意病毒打开大门的措施错误。跟着 .NET Framework 和 CLI 越来越受接待,像 Visual Basic、Fortran、COBOL 和 C# 这样的高级语言都已变得越来越相似,也与 C++ 越来越相似,因为它们都必需支持根基 CLI 观念 – 类、担任、成员函数及其他根基观念。每种语言仍保存其特性,因此措施员不必完全从头进修以利用 .NET Framework;它们只需要进修一些新的观念。
所以,此刻措施员可以用他们所选的任何语言来编写类,其他措施员可以用他们所选的任何语言来利用这些类。任何人可以用任何语言来编写任何组件,只需很少的编程就可以使所有组件无缝地一起事情。它们均受益于安详性、垃圾收集和其他的基本特征(CLI 中的 I)。并且,未来 Redmondtonians 添加新的 CLI 特征后,所有语言也都将从中受益。有了 Windows Vista™ 和 .NET Framework 3.0(具有与 Windows Presentation Foundation、Windows Communication Foundation、Windows Workflow Foundation 和 Windows CardSpace™ 这样的新技能相接洽的 10,000 个新类),Windows 自己正被作为 CLI 类重写。可重用的、语言无关而且可互操纵的组件的方针好像已经最终到达了。这代表了庞大的模式转变,并且令人惊奇地以一种相对渐进、进化的方法举办。您应该兴奋!编程简直是变容易了。
假如您回顾注视,就会发明 C++ 怎么能不参加到这个“勇敢的新世界”呢?C++/CLI 就插手进来了!没有 C++/CLI,C++ 就会成为不能用于编写 Windows 措施的仅有的现代编程语言,处于孤独的位置。C++ 大概会迟钝消亡,或至少严重地被边沿化。C++/CLI 确保不会产生那样的环境。它担保了热爱 C++ 的措施员(像我)可以在新的时代继承利用它。图 2 说明白从物理呆板到虚拟机的模式转换,以及“C++”在那里适合和不适合“/CLI”。
图 2编程是奈何演变的
竣事语,尽量不是再见
那么,此刻有一条出格的动静。这是最后一次在“事情中的 C++”中写入(键入?)“您忠实的”。我的专栏要退休了。我喜欢把这当作是为了祝愿我长寿百岁 – 您知道,就像团队让很多优秀的运带动引退的方法?我的分开并不会影响 Microsoft 或 MSDN® 杂志对 C++ 所做的理睬。矫正确的是,它反应了这样一个简朴的事实,就是继 164 个专栏(这是我的第 165 个)之后,凌驾了险些 14 年,DiLascia 先生有点累了。并且,这有一个有趣的轶事:执行编辑 Josh Trupin 最近称我为“MSDN 杂志的 Cal Ripken”。哦,我虽不是棒球球迷(我更喜欢足球),但我也知道谁是 Cal Ripken。尽量我很好奇:Cal 到底打了几多场角逐呢?我在 Web 上找到了谜底:2,632. 我还找到一个传记,这样写道“大大都专家都认为 Cal 假如能休息一下的话,将会是一个更棒的运带动”。那么,我把它作为了一个启示。但从不畏惧,这并不料味着我不会再为 MSDN 杂志写稿了。
#p#分页标题#e#
在分开之前,我想感激所有忠实的读者,是他们发来问题、指堕落误和疏漏,使我真实可信,并给出善意的赞扬之词,激昂了我的自尊心,这些我将永远珍惜。尚有一些读者,甚至在颁发之前测试办理方案来辅佐我写专栏。我还想要感激在 MSDN 杂志和我一起事情过的所有了不得的同事们,包罗以前和此刻的,没有特定的顺序:Steve、Josh、Joanne、Eric、Etya、Terry、Laura、Joan(她们俩)、Nancy、Val 和 Milo。我漏下谁了吗?多年来,这些人一直在大力大举支持我,并给了我辽阔的自由空间去写我最想写的内容。我尤其想要感激 Gretchen Bilson。几年前她分开了,但她是在 1993 年反聘我的人,当时 MSDN 杂志照旧《Microsoft Systems Journal》。感谢,Gretchen!