| |||
| 一。const c++的const概念,个人感觉非常重要。但是,总觉得还是不够彻底。 当你写下面这个代码的时候: 代码:
如果b凑巧指向了对象a,那么,a就还是可能变化。 同样,我们也不知道f函数内部是否会改动i变量。 这一切仍然象没有const控制一样地需要程序员自己来保证。 究根结底,我们虽然在函数的入口可以限制对每个参数的读写访问,但是,对更加难以控制的全局环境,语言并没有提供控制。你虽然可能被限制写某一个参数,但是没有办法限制你去写一个静态变量,去写一个全局变量,或者去改变堆的状态。 下面,考虑一个对全局环境的const控制方案。规则如下: 代码:
而这个const对静态方法和全局函数没有意义。 而cg中,这个const对静态函数,全局函数,成员函数都是有意义的。它的意义代表:对除了这些参数之外的全局名字只允许const操作。 对跟c++中的const方法等价的如operator==的原型如下: 代码:
所谓全局名字,代表的是这个函数scope外面定义的对象。它也包括全局堆。 一个const函数只能调用其它的const函数。 从原始操作开始,我们来递归定义const操作: 1。所有读操作都是const操作。 2。赋值是const操作。这可能有点奇怪。实际上int类型的赋值的原型如下: 代码:
3。于是,所有+=, -=, ++等操作都是对环境const的。虽然它们的this不是const的。 4。new不是const的。因为它改变了全局堆的状态。 5。动态数组的拼接不是const的,因为它虽然不改变参与计算的对象的状态,但是它改变全局堆的状态。 动态数组相加的原型如下: 代码:
代码:
代码:
代码:
有了这个const,我们就可以得到fp意义上的真正的无副作用的函数了。同时,也不需要牺牲命令式语言中很方便的for loop等construct。 回到开头提到的例子中,如果我们把f()函数改成const的,我们就可以确信i和a都不会改变: 代码:
c++中const可以表示一个本来就不可变的常量,如1, "abc"等,也可以用来表示一个变量,只不过不能通过这个用const限制的名字来改变。 这在模板的编译期计算时就显得模糊了两种应该区分开的概念。 模板的参数必须要在编译时可知。 c++通过编译器自动推导来区分这两者,语法上不提供区分的方法。 cg中提供literal关键字来区分常变量和常量。 一个literal的名字,其值必须是编译时可知的。而const的名字则未必。 只有literal的名字,即常量才可以用作模板的参数。 下面从原始操作开始定义literal。 1。所有字面整形常量都是literal的。 2。所有字面指针常量,如int*.null都是literal。 3。所有字符串常量,如"hello world"都是literal。字符串常量的类型是const char[k] (这个k是这个字符串的长度) 4。数组literal。如[1,2,3]是const int[3]类型的literal。 5。数组literal的下标读是literal。如"hello"[0]是值为'h'的const char类型的literal。 6。对指针的解引用不属于literal。所以*p, p[0]都不是literal。 7。所有带非const操作的表达式都不是literal。 8。一个literal名字必须初始化。而且必须用literal表达式初始化。 8。动态数组literal。如: 代码:
代码:
10。literal名字可以用来赋值给或者初始化const名字。但是const名字不能用来赋值给literal名字。 支持literal函数。 一个literal函数原型的例子如下: 代码:
1。只能使用literal操作。这就意味着,不能对指针解引用,不能使用赋值操作等non-const操作。 2。可以调用全局函数,或者成员函数。但是只能调用literal函数。 3。类里面定义的constructor都是literal的。这是因为construct内部只允许初始化操作和其它literal操作。 4。对数组的只读循环可以通过foreach来做。如: 代码:
代码:
总结一下: literal函数是要求最苛刻的函数。完全就是fp里面的函数和数学里面的函数的概念。它要求内部不能有任何non-const动作,也不能有指针的解引用和对象的取地址。它只能调用literal函数。(包括构造函数) const函数要求不能对函数外面的环境,这包括全局堆。但是它可以改变函数内部变量的状态。 const函数可以调用其它const函数,也可以调用literal函数。 三。编译时计算。 有了literal函数,我们就可以大大增强我们的编译时计算的能力。 literal函数的特点保证了: 1。如果传入的参数都是literal的话,返回值也肯定是编译时可知的literal。 2。如果传入的参数不是literal的话,返回值可以在运行时获得。 这展示了一个把c++中meta-programming和普通运算统一的前景。 比如说,经常用作meta-programming例子的阶乘。c++中,对静态和动态你需要写两个版本的代码。模板代码无法使用在运行时,而普通的函数代码却不能用在编译时。(虽然有些优化可以对普通函数做静态展开,但是,它的结果不能参与其它的编译时计算) 在cg中,我们可以这样写factorial: 代码:
。现在,只需要维护一份代码。而且,这样的代码比meta-programming的代码容易写,也容易读很多。c++模板的另一个局限是,只能用整形类型作为参数,所有的自定义对象都不能作为参数。 这在cg中也不再是个障碍。既然constructor的结果静态可知,literal函数的结果也静态可知,那么,我完全可以定义这样的模板: 代码:
上面提到的literal模型要求literal函数是fp式的纯functional函数。这很符合现在嵌在c++中的meta-programming的模型。但是,c++,cg毕竟是一个imperative的语言。不支持gc导致无法支持closure,lamda等fp中的重要支柱。如果literal函数不支持imperative的功能,能达到多大的灵活性只怕是个问题。 c++中是对meta-programming和常规计算要写两套代码。 如果在cg中要对literal的functional和imperative各写一套代码,可能也好不到哪里去。 另一个literal的模型是,要求literal函数是const函数的一个子集,literal函数内部不能使用取地址,指针解引用,只能调用literal函数。 但是,象const函数一样,不限制对函数内部定义的名字的修改。对参数的读写控制由参数的类型决定。 这样,literal函数内部也可以是命令式的。可以支持for循环。factorial函数就可以用c++程序员更熟悉的命令式方式这样写: 代码:
代码:
利弊如何,还有待斟酌。但是我的感觉是,两种模型都是可以实现的。 |
| |||
| const, literal这种很细粒度的类型约束似乎总是会造成一些很不灵活的地方。 先拿c++作例子。 比如说,我需要给我的一个类作一个接受一个泛形回调的成员函数: 代码:
代码:
代码:
但是,问题在于,如果我有以下代码: 代码:
如果使用静态函数,可以如此解决: 代码:
使用traits可能也能通过判断F: perator()到底接受的是不是const来解决问题,但是,复杂度只怕太高。而且,也不能很容易地扩展多个参数的更复杂的情况。这个literal和环境const也有类似的问题。 比如说: 代码:
代码:
代码:
可是,在类型上,却很难表达这种依赖性的const/literal。 经过一次回调的函数call()就丢失了本来可能具有的const/literal属性。这种类型信息的丢失让人很不舒服。 而且,比c++的const函数还要让人头疼的是,这个const/literal无法通过静态函数绕开。 似乎唯一的办法就是把同样的代码给const, literal和普通函数各自重复一遍。 ![]() |
![]() |
| 书签 |
| 主题工具 | |
| 显示模式 | |
| |
相似的主题 | ||||
| 主题 | 主题作者 | 版面 | 回复 | 最后发表 |
| 捕捉鼠标输入的方法 | bankrock | 技术杂烩 | 33 | 2005-06-16 03:32 PM |
| Huge Integer | wqqafnd | 技术杂烩 | 48 | 2005-01-07 04:44 PM |
| 搜索文件的iterator | SpitFire | 技术杂烩 | 2 | 2004-06-01 01:08 PM |
| allaboutprogram.com C++ 初学者FAQ | codinggirl | 技术杂烩 | 4 | 2004-03-22 11:51 AM |
| ABP C++ FAQ02-------------essential const | codinggirl | 技术杂烩 | 1 | 2003-12-18 09:59 AM |