Bjarne Stroustrup's FAQ(中文版)

原作:Bjarne Stroustrup    翻译:Antigloss

        译者的话:尽管我已非常用心,力求完美,但受水平所限,错误在所难免,还请各路高手不吝斧正。邮箱地址:Antigloss at 163 dot com。本译文是对以前叶丰的译文的补充,之前叶丰翻译过的内容我没有重译,故亦没有出现于本页面,想看的朋友可以自行搜索一下。呃……其实有少量译文使用了叶丰的翻译,那些我会用灰色字表明。



目 录

类的伟大之处是什么?

        类可以帮助你组织代码和分析程序。你可以大致理解为,类可以让你避免犯错,而如果你犯错了,类使你易于寻找错误。也就是说,类大大提高了代码的可维护性。

        类是思想和观念的代码形式。类的对象是思想的具体实例在代码上的体现。没有类,读代码的人只能猜测数据和函数的关系——类能清楚地表明这种关系,并且能被编译器理解。有了类,程序的高层结构就更能反映在代码上,而不单单是在注释中。

        一个设计良好的类能为用户提供简洁易用的接口,并将其内部结构隐藏起来,用户根本不必了解其内部结构。如果内部结构不应该被隐藏——例如,因为用户需要随意改变类中的任何数据成员——你可以把这种类认为是“普通的老式数据结构(data structure)”;例如:

                        struct Pair {
                                Pair(const string& n, const string& v) : name(n), value(v) { }
                                string name, value;
                        };

注意,数据结构也可以使用辅助函数,例如构造函数。

        设计类时,思考类有哪些方面在任何时刻对其每个对象都是通用的,这会很有用。这种通用的属性被称之为不变要素(invariant)。例如,vector 的不变要素是,其内部有一个指针,指向一系列元素,这些元素的数目保存于一个整型变量。每个构造函数都有责任去构建类的不变要素,这样成员函数才能依赖这些不变要素。成员函数退出时必须保持不变要素的有效性。这种思维方式对于管理资源的类来说更是特别有益,如管理锁(locks)、sockets 和文件的类。例如,一个处理文件的类的不变要素是,它有一个指针,指向一个打开的文件。该类的构造函数负责打开文件,而其析构函数负责释放构造函数获取的资源。例如,该类的析构函数负责关闭构造函数打开的文件:

                        class File_handle {
                        public:
                                File_handle(const char* n, const char* rw)
                                        { f = fopen(n,rw); if (f==0) throw Open_failure(n); }
                                ~File_handle() { fclose(f); } // destructor
                                // ...
                        private:
                                FILE* f;
                        };

        如果你未曾使用过类,你会觉得这番说明有些地方相当费解并且会低估类的用处。要寻找例子,和所有优秀的教科书一样,TC++PL(C++ 程序设计语言)里有很多例子;例如,漫游标准库。大多数现代 C++ 库都包含类(当然还有其它东西),库使用指南是寻找有用的类的例子的最佳场所之一。

原文地址:http://www.research.att.com/~bs/bs_faq.html#class



什么是面向对象编程?它的伟大之处是什么?

        面向对象(object oriented, OO),面向对象编程(object-oriented programming, OOP),以及面向对象编程语言(object-oriented programming languages, OOPL)的定义多种多样。想了解我对 OO 的详细看法,请阅读 C++ 为什么不仅仅是面向对象的语言。我在那里写道,OOP 这种编程风格起源于 Simula(约 40 年以前!),它依赖于封装(encapsulation)、继承(inheritance)以及多态(polymorphism)。就 C++(及许多其它源于 Simula 的语言)而言,OOP 的意思是利用类层级(class hierarchies)及虚函数进行编程,从而可以通过精制的接口操作各种类型的对象,并且程序本身也可以通过派生(derivation)进行功能增量扩展。

        你可以从类的伟大之处是什么这篇文章了解到“普通类(plain classes)”的伟大之处。把多个类组织成类层级是为了表达类之间的层次关系并且利用这些关系简化代码。

        要想真正理解 OOP,就要看些例子。例如,你可能有两个(或者更多)设备驱动共用一个公共接口:

                class Driver { // 公共驱动接口
                public:
                        virtual int read(char* p, int n) = 0; // 从设备中读取最多 n 个字符到 p
                                                                            // 返回读到的字符总数
                        virtual bool reset() = 0; // 重置设备
                        virtual Status check() = 0; // 读取状态
                };

Driver 仅仅是一个接口。它没有任何数据成员,而其成员函数都是纯虚函数。通过这些接口就可以使用某个驱动;不同类型的驱动负责对这个接口进行相应的实现:

                class Driver1 : public Driver { // 某个驱动
                public:
                        Driver1(Register); // 构造函数
                        int read(char*, int n);
                        bool reset();
                        Status check();
                        // 实现细节
                };

                class Driver2 : public Driver { // 另一个驱动
                public:
                        Driver2(Register);
                        int read(char*, int n);
                        bool reset();
                        Status check();
                        // 实现细节
                };

注意,这些驱动含有数据成员,可以通过它们创建对象。它们实现了 Driver 中定义的接口。不难想象,可以通过这种方式使用某个驱动:

                void f(Driver& d) // 使用驱动
                {
                        Status old_status = d.check();
                        // ...
                        d.reset();
                        char buf[512];
                        int x = d.read(buf,512);
                        // ...
                }

这里的重点是,f() 不需要知道它使用的是何种类型的驱动;它只需知道有个 Driver 传递给了它;也就是说,有一个接口传递给了它。我们可以这样调用 f() :

                void g()
                {
                        Driver1 d1(Register(0xf00)); // create a Driver1 for device
                                                                    // with device register at address 0xf00

                        Driver2 d2(Register(0xa00)); // create a Driver2 for device
                                                                     // with device register at address 0xa00
                        // ...
                        int dev;
                        cin >> dev;

                        if (dev==1)
                                f(d1); // use d1
                        else
                                f(d2); // use d2
                        // ...
                }

注意,当 f() 使用某个驱动时,与该驱动相对应的操作会在运行时被隐式选择。例如,当 f() 得到 d1 时,d.read() 使用的是 Driver1::read();而当 f() 得到 d2 时,d.read() 使用的则是 Driver2::read()。这被称为运行时调度或者动态调度。本例,f() 无法得知调用的是何种设备,因为那是根据输入选择的。

        请注意,OOP 并非万能药。不要简单地把“OOP”等同于“好”。如果你的问题的基本要素中没有与生俱来的层级关系,那么类层级和虚函数对你的代码不会有任何帮助。OOP 的优势在于类层级可以有效地表达很多问题;OOP 的主要弱点在于太多人设法强行用层级模式解决问题。并非所有问题都应该面向对象。也可以考虑使用普通类(plain class)、泛型编程和独立的函数(就像数学、C,以及 Fortran 中那样)作为解决问题的方案。

原文地址:http://www.research.att.com/~bs/bs_faq.html#oop



何谓泛型编程?其伟大之处何在?

        泛型编程(Generic Programming, GP)是一种基于参数化(parameterization)的编程技巧:可以使用类型参数化另一种类型(例如,vector 的元素类型就是通过参数确定的);算法也可以参数化另一种算法(例如,使用比较函数参数化排序函数)。GP 的目的是将有用的算法或者数据结构尽可能地一般化,并使其最优化。例如,如果没有 GP,你必须为整型 vector 专门写一个类;然后,为了寻找其中的最大值,你又得为它写一个专门的函数。但是,使用 GP 可以把一切变得更美好:只需要写一个类,即可拥有任何类型的 vector;只需要写一个函数,即可寻找任何类型 vector 中的最大值。例如:

                vector<string>::iterator p = find(vs.begin(), vs.end(), "Grail");

                vector<int>::iterator q = find(vi.begin(), vi.end(), 42);

这些例子出自标准模板库(ISO C++ 标准库中容器和算法那部分)。TC++PL 中,漫游标准库那章对标准模板库(Standard Template Library, STL)作了简要的介绍。

        GP 在某些方面比 OOP 要灵活得多。特别是,它不依赖于层级。例如,int 和 string 之间没有任何层级关系。总的来说,GP 的结构化程度更甚于 OOP。事实上,GP 常被称为“参数多态(parametric polymorphism)”;而 OOP 常被称为“ad hoc 多态”。就 C++ 而言,GP 于编译时就解析了所有名称;它不需要任何动态(运行时)调度。因此,GP 在对运行时效率要求很高的领域占据了主导地位。

        请注意,GP 并非万灵丹。很多时候,程序并不需要参数化多态,而需要运行时调度(OOP)。

原文地址:http://www.research.att.com/~bs/bs_faq.html#generic



为什么 C++ 允许不安全的代码?

也就是说,为什么 C++ 支持的一些操作能够违反静态(编译时)类型安全机制?

所以,当你并不需要以上三种特性时,最好避免有如瘟疫般的不安全代码:

几乎所有 C++ 代码都能遵循这些简单的规则。请不要因为你编写 C 代码或者 C 风格的 C++ 代码时不能遵循这些规则而认为 C++ 不安全。

原文地址:http://www.research.att.com/~bs/bs_faq.html#unsafe



为了成为真正的 OO 程序员,在学 C++ 之前,我需要先学一门纯 OO 语言吗?

        不。学新东西通常都是一个好主意。然而,每种语言都是不同的,并且带有其独有的风格和倾向。使用某些被认为是“纯” OO 风格的语言编写的代码,总是不能很好地用其它语言模仿,而且,如果硬要逐字转化成 C++ 的话,更是会碰个满头灰。而且,“编写纯 OO 代码”也并非我的理想;请参考我的 OOPSLA 主题演讲:为什么 C++ 不仅仅是面向对象的编程语言。如果你想成为一个优秀的 C++ 程序员,但却抽不出多少时间,那么还是集中精力学习 C++ 和它所包含的观念吧。

antigloss 注:OOPSLA 是 Object-Oriented Programming, Systems, Languages and Applications 的缩写,意为面向对象编程、系统、语言,及应用程序。

原文地址:http://www.research.att.com/~bs/bs_faq.html#learn-pure



为什么 C++ 没有图形用户接口?

        C++ 有很多商用和开源的 GUI(Graphical User Interface,图形用户接口),例如 GtkmmSmartWin++V C++ GUIFLTK,以及 Qt。明确地说,不同平台的提供商提供不同的 C++ 库来访问他们的 GUI。问题就在于没有标准 GUI,而这正是主要问题所在。

        请注意,提供 GUI 不单只是技术问题,它还涉及可行性问题。现在有很多 GUI,而且都有其用户群,他们不会愿意别的 GUI 被声明为标准。无论如何,标准委员会没有足够的资源建立一个更好的新 GUI。

原文地址:http://www.research.att.com/~bs/bs_faq.html#gui



为什么 C++ 不支持线程?

        ISO C++ 标准没有提及并发(concurrency),但所有主要的 C++ 实作都支持线程;pthread 几乎无所不在,而且很多平台提供商都提供他们自己的系统线程。出于可移植性的考虑,可使用一些线程库,例如 Boost 线程库。C++0x 几乎肯定会包含一个线程库。

原文地址:http://www.research.att.com/~bs/bs_faq.html#threads



C++0x 会是什么样的?

        我也不完全清楚。C++0x 将会是持续数年的标准化进程的成果。在这个进程中,会认真考虑 C++ 社群各个不同派别的需要——关于发展方向的讨论正在进行中。C++ 标准将长期保持稳定,而 C++ 语言更是会保持更长期的稳定,因为兼容性始终是首要考虑的问题。

        我个人认为,主要的原则应该是:

改变和扩展应该服务于使 C++ 成为一个更好的系统编程及库建造平台,同时也要使 C++ 更易于教学。毫无疑问,我们会对上面两个句子中的“重要”二字的含义做一些讨论。

        我在 SD2001w 的一个座谈会上简要地表达了我的一些观点,在 2002 年春季的 ACCU(C 和 C++ 用户协会)会议的主题演讲中稍微细节一点地阐述了这些观点。请翻阅我设计 C++0x 的首要准则以及我的发布页中的其它新近论文。WG21 上有一个更完整的提案列表。我的 C++ 页面中有更多关于 C++0x 标准及其冀望列表(wish list)的信息。

        C++0x 中的 'x' 被冀望为 9:C++09,而不是(例如)C++0xA(十六进制)。:-)

原文地址:http://www.research.att.com/~bs/bs_faq.html#When-next-standard



何时会有新的 ARM ?

        Ellis 和 Stroustrup 合著的《带评注的 C++ 参考手册》(The Annotated C++ Reference Manual, ARM),俗称“The ARM”,写于 1989 年。它曾是 C++ 标准化工作的基础,但现在它已经太老了,不能反映当前的 C++ 标准。

        由于种种原因,原计划的描述 ISO C++ 标准的替代品(“ARM++”)没能写成。又因为关于 C++0x 标准的工作已经开始,所以现在写一本基于 ISO C++ 1998 的 ARM++ 已经太晚了。

原文地址:http://www.research.att.com/~bs/bs_faq.html#ARM



你怎么看C#?

        我对C#没有什么评论。你很难说服我为什么世界上需要另一个专有语言,更难说服我为什么还需要一个和特定的专有操作系统结合得那么紧的语言。

        显然,我不是专有语言的爱好者,而是开放的正式的标准的爱好者。

-------------------------------------- 分割线 --------------------------------------

        如果你想专门为 .Net 平台编程,C# 并非最差的选择。记住,.Net 也强烈支持 C++(尽管宣传得不那么多)。

(这句话是 B.S. 新增的,旧的译文中没有)

原文地址:http://www.research.att.com/~bs/bs_faq.html#Csharp



您怎么看待 C++/CLI?

        C++/CLI 是对 ISO C++ 的一组扩展,它将 C++ 和微软的 CLI(通用语言架构 Common Language Infrastructure)极其完整地“捆绑”在一起。它已经通过 ECMA 进行了标准化(ECMA-372)。我很高兴它(C++/CLI)使得 C++ 可以很容易地使用 CLI 的所有特性,也为它远胜于它的前任“托管 C++(Managed C++)”感到欣喜。然而,C++/CLI 为了达到这些目的,却对 C++ 进行了各种语言特性上的扩展,以迎合 CLI 的各种特性(接口、属性、泛型、指针、继承、枚举等很多很多),我对此深表遗憾。这将成为混乱的主要源头。C++/CLI 比 ISO C++ 多出了大量新的语言上的便利,这会诱导程序员(不知不觉地)编写出不可移植的代码,而且这些代码和 MS Windows 却有着亲密接触。

        CLI 提供了一套访问系统设施(facilities)和应用程序的接口,而这套接口却和传统的接口大相径庭。特别是,这套接口的语义不能完全或者便利地用传统的编程语言来表达。一种形容 CLI 的方法是把它当作(局部的)“平台”或者“虚拟机”。它包含一套庞大的语言特性(继承、方法、循环构造 (loop constructs),回调机制 (callback mechanisms) 等),支持一套庞大的基础类库(即 BCL),还有一个精巧的元数据(metadata)系统。CLI 有时也被称为“语言中立者”。然而,如果一种语言不大量引入 CLI 的特性,它甚至不能使用 .NET 的基本设施(或者 MS Windows 将要添加的设施,如果 MS 的计划不变的话)。而且,如果一种语言不能表达所有这些特性,那它就不能应用于这个实作(.NET),因为 .NET 的资源是为其它(支持这些特性的)语言服务的。因此,CLI 是“语言中立者”这种说法是仅仅建立在所有语言为了成为 .NET 上的“一流”,都必须支持 CLI 的所有特性这个基础上的。

        我更希望绑定是通过少数原语(primitive)来完成的,而且这些原语应该能被任何编程语言用简单的函数调用和数据结构来表示,或者把这些原语封装于语言特有的库里。而 CLI 最多只对全盘接受 CLI 设施的语言提供这种支持。为了生成 CLI 模块,编程语言必须能够表达 CLI 的全部特性,包括原数据。只有能做到这些的编程语言才能成为 .NET 的系统编程语言。进而,MS C++ 小组断定,只有内建语言特性才能被用户接受。他们的设计反映出来的思维是:带 C++/CLI 扩展的 C++ 可以使用 CLI 的任何设施;使用 CLI 设施时,(C++/CLI)比其它语言更加简练;和其它语言相比,(C++/CLI)绝无多余花销。他们的目标是使 C++ 成为主流的 Windows 系统编程语言。

        一如既往,我极力强调可移植性,并且建议程序员在设计应用程序时,通过 ISO C++ 中明确定义的那些接口访问系统特定的设施(例如不要直接使用 C++/CLI)。在 Windows 上,这样做有时会比直接使用 C++/CLI 的设施来的麻烦,但这是获取可移植性和降低(平台)销售商依赖度的唯一途径。当然,如果一段代码的目的是为其它代码提供 CLI 接口,那么这种间接使用 CLI 的方法也是难以维护的。请注意,我知道系统特定扩展的必要性,我也知道提供这种扩展的 C++ 销售商并非只有微软,我只是强烈希望这种扩展能通过 ISO C++ 允许的“小巧的接口”来实现。

        如何处理系统特定的扩展是一个固有的难题。微软 C++ 小组,特别是 Herb Sutter,一直都在和 ISO C++ 标准委员会的其他成员讨论这个问题,以期最终理清 ISO C++ 及其超集 C++/CLI 的关系。在 ISO C++ 标准委员会,我们已经作出了许多有建设性的合作。同时,为了避免混淆 ISO C++ 和 C++/CLI 扩展,微软正在修改 VC++ 文档,以期清楚地区分 ISO C++ 和 C++/CLI(单纯写 C++ 即表示 ISO C++)。我希望其他销售商也会跟随这种潮流。

        如何称呼 C++ 的 CLI 绑定/扩展是一个充满争议的难题,我个人倾向于使用 C++/CLI 作为“ISO C++ 的 CLI 扩展”的缩写。在名称中保留 C++ 可以提醒人们(C++/CLI 的)基础语言是什么,并且有助于保持 C++ 为带 C++/CLI 扩展的 C++ 的严格子集。C/C++ 兼容性问题论证了保持子集严格性的重要性。

以下是一些关于C++/CLI的文档:

原文地址:http://www.research.att.com/~bs/bs_faq.html#CppCLI



您如何看待 EC++ ?

        EC++(基本上)是 C++ 去掉异常、模板、命名空间、RTTI 支持、多继承等特性后的一个子集。定义这个子集的是一个“工业联盟”。我不喜欢语言子集或者方言,尤其是那些不支持标准库的子集,因为这样的话,该子集的用户就不得不各自开发不兼容的基础库。我担心定义 C++ 子集会造成用户群体分裂,并引起相互间攻击(1999-3-31:我看到一个广告用生动的图像来显示 EC++ 如何通过废除命名空间、模板、C++ 标准字符串类,以及其它东西来减“肥”(例如内存空间)。叹息)。我强烈希望关于“标准”的工作发生于开放的研讨会(例如 ISO 或者国家标准组织)。

        ISO C++ 委员会的性能报告里有一个关于嵌入式系统实现者如何通过标准 C++(比用方言好)解决性能问题的讨论。就我所知,EC++ 已经死了(2004),而如果它还没死的话,也行将就木。

        想了解 ISO C++ 如何应用于苛刻的嵌入式系统编程,可参考 JSF 航空器 C++ 代码标准

原文地址:http://www.research.att.com/~bs/bs_faq.html#EC++



为何您如此看重可移植性?

        成功的软件都是长寿的;数十年生命周期的软件并不少见。针对某种硬件、操作系统、数据库系统等设计的应用程序/程序,如果设计得当,其寿命通常都比这些东西还要长。通常,一个优秀的软件的寿命会比提供构造这个软件的基本技术的公司的寿命还要长。

        通常,一个成功的应用程序/程序的客户/用户使用的平台会有所不同。而且不同平台的数目会随着用户数目的增加而增加。如果某个应用程序/程序紧密依附于一个单一的平台或者(平台)销售商,那么它就会丢失很多潜在的用户。

        显而易见,完全平台无关性意味着不能使用平台特定设施。然而,通过“小巧的接口”把应用程序眼里的环境包装成库,然后通过这些接口访问平台设施,这样往往就可以构造出近似平台无关的应用程序。

原文地址:http://www.research.att.com/~bs/bs_faq.html#portability



C 是 C++ 的子集吗?

        严格按照数学上的定义来说,C 不是 C++ 的子集。有些程序在 C 里面是合法的,但在 C++ 里却是不合法的;甚至有些编写代码的方式在 C 和 C++ 中有不同的含义。然而,C++ 支持 C 所支持的全部编程技巧。任何 C 程序都能被 C++ 用基本相同的方法写出来,并且运行效率和空间效率都一样。把数万行 ANSI C 代码转换成 C 风格的 C++ 代码,通常只需要几个小时。因此,C++ 是 ANSI C 的超集程度和 ANSI C 是 K&R C 的超集程度以及 ISO C++ 是 1985 年的 C++ 的超集程度差不多。

        编写风格好的 C 程序通常会是合法的 C++ 程序。例如,Kernighan 和 Ritchie 合著的《C 程序设计语言(第二版)》中的所有例子都是 C++ 程序。

C/C++ 兼容性问题的一些例子:

        int main()
        {
                double sq2 = sqrt(2); /* 不是 C++:调用了未经声明的函数 */
                int s = sizeof('a'); /* 隐蔽的区别:C++ 中是 1,而 C 中却是 sizeof(int) */
        }

调用未经声明的函数在 C 里是不良风格,而在 C++ 里是非法的。同样的情况还有,传递参数给一个没有在其声明中列出参数类型的函数:

        void f(); /* 没有注明参数类型 */

        void g()
        {
                f(2); /* C 中是不良风格。C++ 中不合法 */
        }

C 里面,void* 可以隐式转换为任何指针类型,并且堆空间分配通常是通过 malloc() 进行的,无法检查是否已经分配了足够的内存:

        void* malloc(size_t);

        void f(int n)
        {
                int* p = malloc(n*sizeof(char)); /* C++ 中非法。C++ 使用 new 分配堆空间 */
                char c;
                void* pv = &c;
                int* pi = pv; /* 隐式转换 void* 为 int*。C++ 中非法 */
        }

请注意,隐式转换 void* 为 int* 引起了潜在的数据对齐错误。请参阅 C++ 中 void* 和 malloc() 的代替品

把 C 代码转换成 C++ 时,请紧记 C++ 的关键字比 C 多:

        int class = 2; /* C 中合法。C++ 中是语法错误 */
        int virtual = 3; /* C 中合法。C++ 中是语法错误 */

        除了少数例外,如上面所述的例子(以及 C++ 标准和 C++ 程序设计语言第三版附录 B 里详细列举的例子),C++ 是 C 的超集。(附录 B 可以通过下载获取)

        请注意,“C”在以上段落中指的是经典 C 和 C89。C++ 不是 C99 的后裔;C++ 和 C99 是兄弟。C99 引入了一些新特性,造成 C/C++ 的不兼容性进一步增大。这里有一篇 C++98 和 C99 的不同点的描述。

原文地址:http://www.research.att.com/~bs/bs_faq.html#C-is-subset



您真的认为 C 和 C++ 可以合并为同一种语言吗?

        我认为如果它们能合并为同一种语言,对 C/C++ 社群(community)来说实在是再好不过了。也就是说,如果能够系统且完全地消除 C/C++ 的不兼容性,并且有组织地防止它们未来的发展产生新的不兼容性,这就太好了。可能与否那就是另外一回事了。

        我的基本观点是,C/C++ 目前的不兼容性源于“历史的偶然”,而非什么特殊的原因,尽管对于某些能人善士来说,它们“在那时看来都是非常好的主意”。 C/C++ 的不兼容性对 C/C++ 社群普遍没有什么好处,反而对社群中的大部分人造成不少严重麻烦,所以应该通过不懈的努力消除它们的不兼容性。

        欲了解我对 C/C++ 兼容性的看法的更详细的陈述,请参考我就这个问题写的一系列论文:

        我想象着不兼容性(通过同时对 C 和 C++ 进行修改)最终被消除掉后,仍然会存在 C 和 C++ 这两种的语言,但届时,C 就真的可以被称之为 C++ 的子集了

        请注意,这些论文都是 2001 年末到 2002 年初之间写的,那时仍然能够看到一个希望,就是 C 和 C++ 标准委员会相互协作,最终在十年内制定出一个可行的方案。然而,这并没有发生。

原文地址:http://www.research.att.com/~bs/bs_faq.html#merge



您如何看待 C/C++ ?

        不,这并非我常被问到的问题。也就是说,这个问题是这份 FAQ 中的一个“假的 FAQ”。然而,这个问题应该成为一个 FAQ,因为人们在使用“C/C++”时,似乎以为“C/C++”是指某种特定的东西,并且以为他们知道“C/C++”所指的是什么,这导致了很多混淆和苦恼。人们应该询问“什么是 C/C++ ?”,然后深思熟虑并且停止使用这个术语。因为使用这个术语真的会带来不良影响。

        没有任何语言叫“C/C++”。使用这个词的人通常并不了解编程(例如人事和经理)。也就是说,一些根本不懂 C++(并且常常也不懂 C)的人会使用这个词。当程序员使用这个词的时候,常常带有一种这样的态度:“C++ 就是增加了一大堆复杂而弊大于利的特性的 C”。这类人往往对标准库中 printf 和 memcpy 以外的东西知之甚少,喜欢编写他们自己的字符串类和哈希表。还有一些人因为一些无懈可击的原因而坚持仅用 C++ 中的一个特定子集,但他们(就我所知)并不说“C/C++”。

        我个人只在形如“C/C++ 兼容性”之类的词语中使用 C/C++。

原文地址:http://www.research.att.com/~bs/bs_faq.html#C-slash



为何编译 C++ 版的“Hello World”程序生成的代码比 C 版的多十倍?

        在我的机器上并非如此,你的也不应该这样。我甚至曾见过 C++ 版的“Hello world”程序比 C 版的小。最近(2004),我在 Unix 下用 gcc -o2 进行测试,发现两个版本(iostream 和 stdio)的大小完全相同。没有语言层面上的原因会使一个版本比另一个大。这都取决于(编译器的)实现者如何组织库。如果一个版本明显比另一个大得多,那就向产生更多代码的(编译器的)实现者报告这个问题。

原文地址:http://www.research.att.com/~bs/bs_faq.html#Hello-world



为何您把 C++ 设计得和 C(基本)兼容?

        我想让 C++ 兼容一门完整的语言,并使其拥有足以应对乃至无比苛刻的系统编程的性能和灵活性。我惧怕创造出又一种虽然优美,却隐含着一些无心的限制的语言。《C++ 语言的设计和演化》章节 2.7 中有详细的历史细节。“您真的认为……?”中列举的那些文章回顾了 C/C++ 兼容性问题技术上的讨论。

        那时,我认为 C 是可供使用的最优秀的系统编程语言。虽然当时(1979)这(C 的优秀)并不像后来那样显而易见,但是我可以请教 Dennis Ritchie、Steve Johnson、Sandy Fraser、Greg Chesson、Doug McIlroy,以及 Brian Kernighan 等专家并得到他们的反馈。如果没有他们的帮助和建议,也没有 C,C++ 极有可能胎死腹中。

        与谣传相反的是,从来没有人跟我说要我必须使用 C;也没有人跟我说不要使用 C。事实上,第一版 C++ 参考手册便是基于 Dennis 给我的 C 参考手册的 troff 版本完成的。很多新语言都是贝尔实验室设计的;至少在“研究阶段”,没有制定加剧语言偏见的规则。

原文地址:http://www.research.att.com/~bs/bs_faq.html#whyC



C++ 归您所有吗?

        不。如果非要说“C++ 归谁所有”的话,那只能是 ISO。AT&T 将我以前写的 C++ 参考手册的所有权授予给了 ISO。ISO C++ 标准的版权归 ISO 所有。

        C++ 编译器销售商不用支付版税给我和 AT&T。ISO C++ 标准是一份旨在让所有人免费使用的规范文档(当然,这份文档本身是需要付钱向 ISO 或者国家标准委员会购买的)。不同的编译器归不同的销售商/提供商所有。

        “但有 SCO 的人声称 C++ 归他们所有”;难道此言不实? - 这完全是无稽之谈。我看过那次访谈。那个 SCO 的家伙肯定完全不知 C++ 为何物,竟将之称为“the C++ languages”[Antigloss 注:请注意这里使用了 language 的复数]。SCO 顶多拥有已有 15 年历史并早已严重过时的 Cfront - 我最初设计的 C++ 编译器。我过去小心地不让 C++ 和任何专利或商标沾上关系。这是我们书写“C++”而非“C++(tm)”的原因之一。C++ 标准不受任何专利限制 - 标准委员会也对此进行过核实。

原文地址:http://www.research.att.com/~bs/bs_faq.html#revenues



“C++”何得此名?

        《C++ 程序设计语言》(TC++PL)第一章:“C++(读作‘斯加加’)这个名字是 Rick Mascitti 于 1983 年夏天起的。这个名字能充分表明 C++ 是由 C 演变而成的这一进化本质;‘++’是 C 的自增运算符。‘C+’这个稍短的名字是个语法错误;它亦已被用作一种不相关的语言的名字。精通 C 的语义的大师认为 C++ 这个名字比不上 ++C。这门语言没有被命名为 D 是因为它是 C 的扩展,而且它并没有试图通过删减 C 的特性来修正 C 存在的一些问题。想了解 C++ 这个名字的另一种解释,可翻阅 [Orwell,1949] 的附录。”

        《C++语言的设计和演化》(D&E)第三章:“我采用 C++ 这个名字,因为它比较短,很有含义,而且也不是‘什么什么的 C’这种形式的名字。C 语言中,++ 可以根据上下文读成‘next’、‘successor’或者‘increment’,不过通常还是读成“加加”。C++ 这个名字及其竞争对手 ++C 是产生笑话和双关语的肥沃土壤——几乎所有这些笑话和双关语在 C++ 这个名字被采用之前就已为人们所熟知和品味。C++ 这个名字是 Rick Mascitti 建议的。1983 年 12 月,这个名字第一次被使用——它被写入了 [Stroustrup,1984] 和 [Stroustrup,1984c] 的最终版。

        C++ 中‘C’这个字母拥有悠久的历史。显然,它是 Dennis Ritchie 设计的语言的名字。C 的直接祖先是 Ken Thompson 设计的叫做 B 的解释语言,而 B 语言是 BCPL 的后裔。BCPL 是剑桥大学的 Martin Richards 在访问位于另一个剑桥(坎布里奇)的 MIT 时设计和实现的。BCPL 代表 Basic CPL,而 CPL 是一门相当大(就当时而言)而优雅的编程语言的名字,它是剑桥大学和伦敦大学合力开发出来的。在伦敦大学参与进来之前,CPL 中的‘C’代表剑桥。后来,‘C’的正式含义是组合,而它的非正式含义是 Christopher,因为 Christopher Strachey 是主持 CPL 开发的首脑。”

原文地址:http://www.research.att.com/~bs/bs_faq.html#name



您是使用何种语言编写出 C++ 的呢?

        第一个 C++ 编译器(Cfront)是使用 C++ 编写出来的。为此,我首先使用 C 编写了一个将“带类的 C”转换成 C 的预处理器。“带类的 C”属于 C 的一种变种,它是 C++ 的直接祖先。这个预处理器将“带类的 C”的结构成分(例如类和构造函数)翻译成 C。它是一个传统的预处理器,不能完全理解 C++ 语言,还将大多数类型检查留给了 C 编译器来做,而且其对 C++ 成分结构的翻译是单个单个地进行的,而不会先对全局进行分析。然后我利用“带类的 C”编写出了 Cfront 的第一个版本。

        Cfront 是一个传统的编译器,它会对 C++ 源代码进行完整的语法和语义检查。为此,它要有一个完整的词法分析器,要建立符号表,还要为每个类、函数等建立一套完整的内部树状表示。在生成 C 代码之前,它还会对 C++ 成分结构的内部树状表示做一些源代码层面上的优化。虽然这个版本(的 C++ 编译器)生成 C 代码,但它不依赖 C 做任何类型检查。它仅仅把 C 用作汇编器。最终生成的代码非常快。想知道更多信息,可参考 D&E

原文地址:http://www.research.att.com/~bs/bs_faq.html#bootstrapping



为何 C++ 如此庞大?

        C++ 并非有些人想像的那么庞大。它并非为教学目的而设计的小巧语言,但那些人们经常用来和 C++ 比较的语言也不小巧,例如 C、Java,C#。和 Wirth 博士最初定义的 Pascal 相比较,它们也非常庞大。当然,这样设计是有合理的理由的,我这么认为。和 30 年前相比,当今的编程世界已经变得异常复杂,现代编程语言恰恰反映了这一点。

        C++ 标准共有 740 页,但其中有 400 页描述标准库。语言特性的(极度详细的)描述只不过占了 340 页。类似地,TC++PL 共有一千余页,但仅有 350 页用于解释语言特性及其用法;其余部分讨论标准库、编程技巧等。

        C++ 直接支持(也就是内建于语言中)一些其它语言通过库来支持的特性,所以 C++ 的语言部分会相对更庞大一些。另一方面,如果你想编写一个“典型的现代应用程序”,那你还需要考虑操作系统接口、GUI、数据库,网页接口等。你必须熟悉的东西包括语言特性、库,以及编程习俗与标准。和这些东西的总和相比,编程语言本身还是很小的。C++ 的庞大可以说是一个优点,因为它能更好地支持优秀的库。

        最后,编程新手也能了解一门语言的全部特性的时代已经一去不复返了,至少对于那些广泛应用于工业上的语言来说是这样。只有少数专家级的人了解“C 的全部”或者“Jave 的全部”。由此推之,没有人需要为新手不了解 C++ 的全部而道歉。你必须做的是——无论学任何语言——先学其中一个子集,开始编写代码,然后循序渐进地深入学习这门语言,它的库及其开发工具。

原文地址:http://www.research.att.com/~bs/bs_faq.html#big



现在还有人使用 C++ 吗?

        当然,有很多。C++ 的用户虽然多得难以统计,但计量单位肯定是百万级的。所有主要的平台销售商都支持 C++。我的应用程序例举中有一些 C++ 的应用例子。

原文地址:http://www.research.att.com/~bs/bs_faq.html#use-C++



为何 C++ 没被用于编写操作系统?

它已被用于编写操作系统有些年头了(超过十年);请参考我整理的 C++ 应用程序列表

原文地址:http://www.research.att.com/~bs/bs_faq.html#use-C++-for-OS



有什么好的认证是面向 C++ 程序员的吗?

        就我所知,没有什么好的面向 C++ 程序员的认证。很遗憾。一个好的认证是最有用的。然而,C++ 缺乏提供可靠认证的中央机构,而且,一个不权威的认证或者专注于语法的认证毫无用处。

原文地址:http://www.research.att.com/~bs/bs_faq.html#certification



为什么你不回复我的电子邮件?

        我会回信,但是我收到的信太多了。我估计我回了95%以上的信。但是有时候我会被信件淹没。有些信丢失了,有些要等到有空的时候才回,有些则要等到我回复一批相关信件的时候(经常是对我的书中潜在错误的评论)。不幸的是,越长、越有内涵的信,往往会比问题简单答案也简单的信拖得更久。

        还有,如果你写信给我,请确认我能够给你回信。我很讨厌在我写完并且发出回信后,发现回信地址无效或者无法访问。这种事情每周都会发生。

        相对来说,这样两种来信被忽略的可能性比较大:关于家庭作业的问题和类似“我怎么使用某某库?”的问题。对于不回答后一个问题,我感到有点遗憾,因为提问者经常不明白DOS、Windows或者其他任何C++编程接口并不是C++标准的一部分(并且我也不可能跟上这么多的C++库的发展)。如果你没有收到回信,那么请先考虑一下你的问题是不是上面这几种类型。

Antigloss 注:以下一段是我补译的。因为这段是 B.S 新增的,原来的中文翻译中没有。

        此外,除非你注明你的名字,否则我不会读你的邮件,并且会将其删掉。这是一个新政策。我从来就不喜欢假名。我发现和那种自认为使用类似 suuupergeeek 或者 coolGuy3 这种名字很酷的人,很难进行礼貌的技术交流,所以我不想费心尝试和这种人交谈。

原文地址:http://www.research.att.com/~bs/bs_faq.html#email



“bjarne”是冒名顶替的吗?

        或许不是。大多数新闻组帖子、访谈等,如果声称是我的,那的确是我的。不过,一个显而易见的例外是那篇声名狼藉的 IEEE “访谈”。在我看来,这篇“访谈”非常无聊。我用 gmail 账号在新闻组上发帖,使用的用户名是“Bjarne”,这是为人们所熟知的。可不知何故,有些人好像被它搞糊涂了。如果你有所怀疑,请思量那份可疑信息的风格和内容,并且和论坛中(我的)其它帖子比较,或者发问。

原文地址:http://www.research.att.com/~bs/bs_faq.html#impostor



那真是你说的吗?

        是的,我是说过“C容易让你开枪时不小心打到自己的脚,C++虽然不这么容易,但是你如果真这么做了,它会把你整条腿轰掉”。但人们往往会忽视,我对C++所说的话在不同程度上对于任何强大的语言都是正确的。当你防止人们犯简单的错误时,他们就会开始犯新的、不那么明显的错误。那些能避免简单错误的人,可能会直奔不那么简单的错误而去。对于坚固的保护性的环境,有一个问题,就是难题总是被太晚发现,以至于一旦发现就难以补救。并且,罕见的问题比常见问题更难发觉,因为你往往不会怀疑到它。

        我也说过“在C++里面存在着一个更小一些和更清晰一些的语言,它正在挣扎着浮现出来”。比如,原文在The Design and Evolution of C++的207页(译注:中文版156页)就能找到。不过这个更小更清晰的语言不是JavaC#。原文是在名为“Beyond Files and Syntax”的小节里。那里我指的是C++的语义要比它的语法清晰得多。我是在考虑一种编程风格、程序库和编程环境,相对关注于C的低级层面的古老用法而言,它们强调更清晰更有效的编程实践。

----------------------------------- 分割线 -----------------------------------

        “我一直希望我的电脑可以像我的电话那么易用;我的愿望已经实现了,因为我已经不知道怎么使用我的电话了”。大约在 1990 年,在尝试使用一个“功能强大”的电话受挫后,我说了这句话。我敢肯定这种心情并非为我独有,甚至这句话也可能并非我的原创;肯定有人在我之前就已经这么想了。

        “只有两种语言:一种被人抱怨,而另一种没人使用”。是的。同样,我也非常怀疑这种心情是否为我独有。当然,必须持怀疑态度对待所有“只有两种……”的引证。

        “用类推来证明其实是欺骗行为”。是的;TC++PL 第 692 页。优秀的类推是阐述观点的极好的途径,但很多时候,这些类推却没有可靠的论据,数据等。

        “自以为是的人总会让自知的人觉得厌烦”。是的。

        “C++ 是我最钟爱的垃圾回收语言,因为它几乎不产生垃圾”。是的;请参考为什么 C++ 没有垃圾回收机制?以及我如何处理内存泄漏

        “如果你认为这很简单,那么你已经误解了这个问题”。是的,但我不记得这句话是回答哪个问题的了。

        “使用被视为很糟糕的语言开发出来的系统有很多,比那些用满载赞誉的美丽语言开发出来的系统要多得多。” 是的,在 MIT 技术回顾访谈中和别的地方。我同时也说了:

        很多时候,“计算机科学”是数学狂热的一种形式。是的,但请不要忽略“很多时候”这个词。说这句话时的背景是:我正在担心人们对数据的关注不足(经验主义),而且对计算机科学和计算的连接也不够关注。这并非对“计算机科学可以是科学”这种观念的正面攻击(以 Knuth 的算法及算法分析为例;那就是一门科学,并且不属于任何其它科学)。呃,不,我不打算举具体的例子或名字。

        很多时候,“软件工程”既不是工程学也不涉及软件。是的,但请不要忽略“很多时候”这个词。说这句话时的背景是:我正在担心人们对数据的关注不足(经验主义),而且对软件工程和代码的连接也不够关注。我担心必须交付有用的可维护代码这个事实可能会淹没于一系列步骤、共同标准和市场营销中;我也担心软件开发有时会受控于一些不能分辨代码好坏并以此为豪的人。呃,不,我不打算举具体的例子或名字。假若实践得当,软件开发会是一门有价值的工程学科,它会使工程原理推陈出新。

Antigloss 注:分割线前的译文乃叶丰翻译的,而其后的译文则是我翻译的。

原文地址:http://www.research.att.com/~bs/bs_faq.html#really-say-that