读《C++语言的设计与演化》及一些疑问的解答

C++语言的设计与演化是C++作者Bjarne Stroustrup撰写的一本关于C++从构思设计到实际实现中思考权衡的过程的书,也(应该)是市面上唯一一本语言设计者站在语言设计的视角所写的书。

有很多问题我们不应该只知道How,更应该知道Why,因为这样可以从更深层次地理解这个东西。所幸的是《C++语言的设计与演化》就是这么一本书。最近在读期间明白了很多之前在C++中只知道How而不知道Why的东西(为了与C兼容C++真是割舍了太多),这篇文章算是一篇读书笔记和关于Why的记录,我会逐步整理出来。

什么是面向对象编程?

·····这篇有关于“什么是”的论文(What is ‘‘Object-Oriented Programming’’? (1991 revised version))则定义了一组我认为指出数据抽象和面向对象程序设计的语言应该解决的问题,并给出了所需要的语言特征范例。
结果是重申了C++的“多范型”性质的重要性:
“面向对象的程序设计是利用继承机制的程序设计。数据抽象是使用用户定义类型的程序设计。除了少许例外,面向对象的程序设计将能够而且应该支持数据抽象。这些技术需要有效的正确支持。数据抽象从本质上需要在语言特征的形式上得到支持,而面向对象程序设计则更进一步地需要得到程序设计环境的支持,为了达到通用性,支持数据抽象或者面向对象的语言又必须能有效利用传统硬件。”

更多关于C++和面向对象的内容可以看这几篇文章:
Why C++ is not just an Object-Oriented Programming Language
What is ‘‘Object-Oriented Programming’’? (1991 revised version)
以及SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)

为什么C++没有垃圾回收(GC)机制?

在1985年之前的一段时间里也考虑过垃圾回收的可能性,但后来还是相信,对一个已经被用在实时处理和硬核心系统(例如设备驱动程序)的语言而言,这种特性是不合适的。在那段日子里垃圾回收还没有今天这样复杂,而与今天的系统相比,一般计算机的处理能力和存储能力也是非常低的。

如果带类的C(甚至是C++)被定义成需要垃圾回收的语言,它一定会更优雅些,但也会是一个死胎。

C++与C的关系?

···兼容性问题的另一个方面更要紧:"C++应该以什么方式与C不同,以便达到它自己的目标?"还有”C++应该以什么方式与C兼容,才能达到它的目标?“

···一个共同的意见逐渐浮现出来:在C++和ANSI C之间不应该存在无故的不兼容性[Stroustrup,1986],而确实应该有一些不兼容性,只要它不是无故的。···这个原则后来被广泛地理解为:C++,尽可能地与C靠近,但又不过分靠近。

例如下面的代码,在C中是合法的:

1
2
3
4
5
6
7
8
9
10
11
12
struct outer
{
struct inner
{
int i;
};
int j;
};
int main(int argc,char* argv[]){
struct inner x={1};
printf("%d\n",x.i);
}

但是在C++中却是非法的:

error: variable has incomplete type 'struct inner'

因为在C++中具有命名空间(此处类作用域)的概念,在C++中这么写才对:

1
2
3
4
5
6
7
8
9
10
11
12
struct outer{
struct inner{
int i;
};
int j;
};
int main(int argc,char* argv[]){
// 当然在C++中此处的struct写不写也是无所谓的
// 我通过对比两者的中间代码(LLVM/Clang中的"-S","-emit-llvm")发现二者并无区别
struct outer::inner x={1};
printf("%d\n",x.i);
}

还有类似这样的:

1
2
3
4
5
6
struct outer{
}val;
int main(int argc,char* argv[])
{
printf("%d\n",sizeof(val));
}

这份代码在C和C++的编译器(GCC/G++)下编译运行的结果是完全不同的(GCC:0/G++:1)。
看下列中间代码:

1
2
3
4
5
6
7
// GCC
%struct.outer = type {}
@x = common global %struct.outer zeroinitializer, align 1

// G++
%struct.outer = type { i8 }
@x = global %struct.outer zeroinitializer, align 1

事实上在用G++编译(C++方式编译)outer时,他有一个隐藏的1byte大小,那是被编译器安插进去的一个char。这使得这一个class的两个对象在内存中具有独一无二的地址。(深度探索C++对象模型P84)

因为 C++ 中由于实例化的原因(空类同样可以被实例化),每个实例必须在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后就能在内存得到了独一无二的地址。

通过查阅C++标准(ISOIEC:144882:2014(C++14))找到了这个定义(Classes-P214):

Complete objects and member subobjects of class type shall have nonzero size. [^108][Note: Class objects can be assigned, passed as arguments to functions, and returned by functions (except objects of classes for which copying or moving has been restricted; see 12.8). Other plausible operators, such as equality comparison,can be defined by the user; see 13.5. —end note ]
[^108]: Base class subobjects are not so constrained.

可以看到脚注108[^108]所描述的这种情况:

1
2
3
4
5
6
class X{};
class Y: X{};
// 此时使用sizeof来计算X和Y的大小应该是多少?
cout<<sizeof(X)<<endl
<<sizeof(Y)<<endl;
// 输出结果均为 1

C++03和C++11发布时间间隔久远的原因(TC++PL4)

[TC++PL 4th]One reason for the long gap between the two standards is that most members of the committee(including me) were under the mistaken impression that the ISO rules required a ‘‘waiting period’’ after a standard was issued before starting work on new features. Consequently, serious work on new language features did not start until 2002. Other reasons included the increased size of modern languages and their foundation libraries.

全文完,若有不足之处请评论指正。
本文标题:读《C++语言的设计与演化》及一些疑问的解答
文章作者:ZhaLiPeng
发布时间:2016年09月09日 08时15分
本文字数:本文一共有1.5k字
原始链接:https://imzlp.me/posts/30227/
许可协议: CC BY-NC-SA 4.0
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!