WEB开发网
开发学院软件开发C++ 工作中的C++:基本原理,重点推荐和结束语 阅读

工作中的C++:基本原理,重点推荐和结束语

 2010-10-15 09:07:33 来源:Web开发网   
核心提示:基本原理噢,我在查找回复的过程中,工作中的C++:基本原理,重点推荐和结束语(2),偶然发现了一篇很好的文章,称为《A Design Rationale for C++/CLI》(C++/CLI 设计基本原理),熟悉的构造/析构模式就会像您预期的那样运行,Redmondtonians 值得信任,出自 Microsoft

基本原理

噢,我在查找回复的过程中,偶然发现了一篇很好的文章,称为《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 功能)考虑的替代的语法,以及它们被拒绝的原因。

抛弃托管扩展

“基本原理”还解释了为什么 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 值得信任,因为他坦白承认托管扩展存在的问题 - 还因为他处理了这些问题。

Tags:工作 基本 原理

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接