与值语义对应的,有人叫它对象语义,有人叫它引用语义。考虑到我主要关注它的可变性而不是发消息方面的特性,所以这里选择引用语义的称呼。
我现在越来越怀疑specpaxos选择用C++实现,会不会只是因为世界上只有这一门C++,对值语义和引用语义的支持都充分至极,哪怕因此造成语义一团混沌也在所不惜。
当然,如我们所知,没有值语义的语言也可以把引用语义当值语义来用。只是偶尔要记得这一切都不是真的。
但如果是没有引用语义的语言,这事就不好办了。
前几天看到网上资料里写着,C++中值语义是默认行为,我还觉得深以为然。
其实引用语义也没有 不默认 到哪里去吧?!唯一的区别就是变量声明的时候多出一个&
,这真的很不方便吗。
哪怕同样自由的C语言也啰嗦得多吧。
更不用提我在琢磨的Rust了。
在Rust里使用引用语义,最大的障碍就是使用引用。
Rust真有你的,硬是把这么一句本来是废话的话给变得富有意义。
Rust里的引用要考虑可变性,要考虑生命周期。引用不能比对象活得久,引用也不能允许多个更新互相打架。
这是好的。这当然是大好事。这断绝了几乎所有的内存错误,怎么不是好事?
但有的时候,人们用C++写程序,真不是因为那时候还没有Rust(当然,由于那时候确实没有Rust,所以你也不好证明这一点),而是因为用Rust写不出来啊。
我承认,如果一个模型当中,对于对象生命周期的掌握,对于对象独占所属的控制,如果超出了Rust语义的表达能力,那么可以自信地认为它是 不直观 的。
事实上这也是我对specpaxos的初期印象的精确描述。
它包含了一个完善且典型的面向对象的架构。自顶而下的整体设计,无论是那种浑然一体的美感,还是后人在加需求的时候痛苦地在模型上面搞破坏的屎山感,都是如此的经典,如此的面向对象。
我确实花了一阵子才理清楚它的生命周期管理。这其实已经在Rust语境下给它判了死刑。从内存管理的角度来讲,它已经太复杂了。没有可维护性了。
但是,从很多角度来看,这个模型却又是很直观的!
事实就是这样。内存管理不是一个程序的全部。当程序本身的复杂度上升的时候,程序员甚至根本就没有心思去管理内存生命周期了。
一般来说这就是脚本语言的场景了。但如果我们又不能放弃性能呢?
那么这时候就是我们要对编译器讲你TMD少管闲事的时候了。
如果采用了直观的内存模型,就要付出 宏观的编程模型的复杂度 的代价,换做是你你怎么选。
我不否认一定有聪明人。他们可以设计出超级牛逼的模型,又直观,又具有对Rust友好的内存模型。
也许再给我一阵子我自己也能做这个聪明人。
但这真的让人感到泄气。不谈客观上的性价比,不谈客观上的可行性,适用性。就只是简单的,主观上的令人泄气。
就像我之前写的。Rust无不浪漫,它从来不是我梦中的语言。
最后写点具体的。
值语义的一个大问题就是对多态不友好。
trait A {}
let some_a: Vec<Box<dyn A>> = vec![];
当一个对象被加入some_a
的时候,它的类型就被抹除了,只能强行通过RTTI找回来,这是很不美好的。
在C++中,我们可以通过引用语义弥补这个瑕疵
class A {};
class B : public A {};
vector<A *> some_a;
B *a_b = new B;
some_a.push_back(a_b);
// 继续通过a_b来获取对象作为B的身份
而到了Rust里面,我也不用写出来都明白,对象的生命周期已经被转移给列表了,多态列表选择忘掉对象的类型以后,这个对象的类型就被全世界遗忘了。
当然了,内存也安全了。
但我不开心了。