GP技术的展望——C--

longshanks 2008-08-02 09:02:47
声明一下,论坛编辑器吞没了我所有的链接,我不想再重新加了,太多了。如果想要看链接的,请移步我的blog:http://blog.csdn.net/longshanks


GP技术的展望——C--

莫华枫


C++的复杂是公认的,尽管我认为在人类的聪明智慧之下,这点复杂压根儿算不上什么。不过我得承认,对于一般的应用而言,C++对程序员产生的压力还是不小的。毕竟现在有更多更合适的选择。仅仅承认复杂,这没有什么意义。我不时地产生一个念头:有什么办法既保留C++的优点,而消除它的缺点和复杂。我知道 D语言在做这样的事情。但是,D更多地是在就事论事地消除C++的缺陷,而没有在根本上消除缺陷和复杂性。
一般而言,一样东西复杂了,基本上都是因为东西太多。很显然,C++的语言特性在众多语言中是数一数二的。于是,我便想到或许把C++变成“C--”,可以治好C++的复杂之病。在探讨这个问题之前,让我们先从C出发,看看C++为何如此复杂。

C和C++
尽管存在这样那样的不足,比如non-lalr的语法、隐式的指针类型转换等,但C语言的设计哲学却是足够经典的。C语言有一个非正式的分类,认为它既非汇编这样的低级语言,也非Pascal那样的高级语言,而应该算作中级语言,介于其他两类语言之间。这种分类恰如其分地点出了C语言的特点和理念:以高级语言语法形式承载了低级语言的编程模型。低级语言的特点是可以直接地描述硬件系统的结构。C则继承了这个特点。C语言直观地反映了硬件的逻辑构造,比如数组是内存块,可以等价于指针。在C语言中,我们可以几乎直接看到硬件的构造,并且加以操作。这些特性对于底层开发至关重要。
然而,C的这种直观简洁的模型过于底层和琐碎,不利于应用在那些构造复杂、变化多样的应用中。针对C的这些弱点,Bjarne Stroustrup决心利用OOP技术对C语言进行改造,从而促使了C++的诞生。C++全面(几乎100%)地兼容C,试图以此在不损失C语言的直观和简洁的情况下,同时具备更强的软件工程特性,使其具备开发大型复杂系统的优势。这个目标“几乎”达到了,但是代价颇为可观。
在经历了80、90年代的辉煌之后,C++的应用领域开始退步。一方面,在底层应用方面,C++的很多特性被认为是多余的。如果不使用这些特性,那么 C++则同C没有什么差别。相反这些特性的使用,对开发团队的整体能力提出了更高的要求。因而,在最底层,很多人放弃了C++而回归了C,因为那些高级特性并未带来很多帮助,反而产生了很多负担。另一方面,在高层开发中,业务逻辑和界面也无需那么多底层的特性和苛刻的性能要求,更多简单方便、上手容易的语言相比C++更加适合。C++的应用被压缩在中间层,随着业界系统级开发的不断专业化,C++的使用规模也会越来越小。(当然,它所开发的应用往往都是关键性的,并且是没有选择余地的)。实际上,C++在这个层面也并非完美的工具。目前无法取代是因为没有相同级别的替代品。D或许是个强有力的竞争者,但一方面出于历史遗留代码的规模和应用惯性,另一方面D也并未完全解决C++面临的复杂性问题,D也很难在可见的将来取代C++。
实际上,C++的这种尴尬地位有着更深层次的原因。C++的本意是在保留C的底层特性基础上,增加更好的软件工程特性。但是,C++事实上并未真正意义上地保留C的底层特性。回顾一下C的设计理念——直观而简洁地反映底层硬件的特性。C++通过兼容C获得了这种能力。但是这里有个问题,如果我要获得C的这种简单直观性,就必须放弃C++中的很多高级特性。这里最明显的一个例子便是pod(plain old data)。
在C中压根没有pod的概念,因为所有的对象都是pod。但是,C++中有了pod。因为C++的对象可能不是一个pod,那么我们便无法象在C当中那样获得直观简洁的内存模型。对于pod,我们可以通过对象的基地址和数据成员的偏移量获得数据成员的地址,或者反过来。但在非pod对象中,却无法这么做。因为C++的标准并未对非pod对象的内存布局作出定义,因而对于不同的编译器,对象布局是不同的。而在C中,仅仅会因为底层硬件系统的差异而影响对象布局。
这个问题通常并不显而易见。但在很多情况下为我们制造了不小的障碍。比如,对象的序列化:我们试图将一个对象以二进制流的形式保存在磁盘中、数据库中,或者在网上传输,如果是pod,则直接将对象从基地址开始,按对象的大小复制出来,或传输,或存储,非常方便。但如果是非pod,由于对象的不同部分可能存在于不同的地方,因而无法直接复制,只能通过手工加入序列化操作代码,侵入式地读取对象数据。(这个问题不仅仅存在于C++,其他语言,如java、C# 等都存在。只是它们没有很强烈的性能要求,可以使用诸如reflect等手段加以处理)。同样的问题也存在于诸如hash值的计算等方面。这对很多开发工作造成不小的影响,不仅仅在底层,也包括很多高层的应用。
究其原因,C++仅仅试图通过机械地将C的底层特性和OOP等高层特性混合在一起,意图达到两方兼顾的目的。但是,事与愿违,OOP的引入实际上使得C的编程模型和其他更高级的抽象模型无法兼容。在使用C++的过程中,要么只使用C的特性,而无法获得代码抽象和安全性方面的好处,要么放弃C的直观简洁,而获得高层次的抽象能力。反而,由于C和OOP编程模型之间的矛盾,大大增加了语言的复杂性和缺陷数。

舍弃
但是,我们可以看到在C++中,并非所有的高级特性都与C的底层特性相冲突。很多使用C而不喜欢C++的人都表示过他们原意接受OB,也就是仅仅使用封装 。对于RAII,基本上也持肯定的态度。或许也会接受继承,但也表露出对这种技术带来的复杂性的担心。动多态是明显受到排斥的技术。显然这是因为动多态破坏了C的编程模型,使得很多本来简单的问题复杂化。不是它不好,或者没用,是它打破了太多的东西。
因而,我们设想一下,如果我们去除动多态特性,那么是否会消除这类问题呢?我们一步步看。
动多态的一个基本支撑技术是虚函数。在使用虚函数的情况下,类的每一次继承都会产生一个虚函数表(vtable),其中存放的是指向虚函数的指针。这些虚函数表必须存放在对象体中,也就是和对象的数据存放在一起(至少要关联在一起)。因而,对象在内存里并不是以连续的方式存放,而被分割成不同的部分,甚至身首异处(详见《Inside C++ Object Model》)。这便造成了前面所说的非pod麻烦。一旦放弃虚函数和vtable,对象的内存布局中,便不会有东西将对象分割开。所有的对象的数据存储都是连续的,因而都是pod。在这一点上,通过去除vtable,使得语言回归了C的直观和简单。
动多态的内容当然不仅仅是一个虚函数,另一个重要的基石是继承。当然,我们并不打算放弃继承,因为它并不直接破坏C的直观性和简洁性。不同于虚函数,继承不是完全为了动多态而生的。继承最初的用途在于代码复用。当它被赋予了多态含义后,才会成为动多态的基础。以下的代码可以有两种不同的解读:
class B : public A {};
从代码复用的角度来看,B继承自A,表示我打算让B复用A的所有代码,并且增加其他功能。而从多态的角度来看,B是一个A的扩展,B和A之间存在is-a的关系。(B是一个A)。两者是站在不同层面看待同一个问题。代码复用,代表了编码的观点,而多态,则代表了业务逻辑的观点。但是,两者并非实质上的一回事。在很多情况下,基类往往作为继承类的某种代表,或者接口,这在编码角度来看并没有对等的理解。而这种接口作用,则是动多态的基础。动多态通过不同的类继承自同一个基类,使它们拥有共同的接口,从而可以使用统一的形式加以操作。作为一个极端,interface(或者说抽象基类),仅仅拥有接口函数(即vtable)而不包含任何数据成员。这是纯粹的接口。
然而,这里存在一个缺陷。一个接口所代表的是一组类,它将成为这一组类同外界交互的共同界面。但是,使用基类、或者抽象基类作为接口,实质上是在使用一个类型来代表一组类型。打个比方,一群人凑在一起出去旅游,我们称他们这群人为“旅行团”。我们知道旅行团不是一个人,而是一个不同于“人”的概念。动多态里的接口相当于把一个旅行团当作一个人来看待。尽管这只是逻辑上的,或许一个旅行团的很多行为和一个人颇为相似。但是根本上而言,两者毕竟不是相同层次的概念。这样的处理方法往往会带来了很多弊端。
为了使继承被赋予的这重作用发挥作用,还需要一项非常关键的处理:类型转换。请看以下代码:
void func(A* a);
B b;
func(&b);
最后这行代码施行了动多态,如果B override了A的虚函数的话。很显然,如果严格地从强类型角度而言,&b是不应当作为func的实参,因为两者类型不匹配。但是如果拒绝接受&b作为实参,那么动多态将无法进行下去。因此,我们放宽了类型转换的限制:允许继承类对象的引用或指针隐式地转换成基类的引用或指针。这样,形如func(&b);便可以顺理成章地成为合法的代码。
然而,这也是有代价的:
B ba[5];
func(ba);
后面这行函数调用实际上是一个极其危险的错误。假设在func()中,将形参a作为一个类型A的数组对待,那么当我们使用ba作为实参调用func()的时候,会将ba作为A的数组处理。我们知道,数组内部元素是紧挨着的,第二个元素的位置是第一个元素的基址加上元素的尺寸,以此类推。如果传递进来的对象数组是B类型的,而被作为A类型处理,那么两者的元素位置将可能不同步。尽管B继承自A,但是B的尺寸很有可能大于A,那么从第二个元素起,a[1]的地址并非ba[1]的地址。于是,当我们以a[1]访问ba时,实际上很可能在ba[0]的内部某个位置读取,而func()的代码还以为是在操作ba[1]。这便是C++中的一个重要的陷阱——对象切割。这种错误相当隐蔽,危险性极大。
由于C++试图保留C的编程模型,因而保留了指针-数组的等价性。这种等价性体现了数组的本质。这在C中是一项利器,并无任何问题。但在C++中,由于存在了继承,以及继承类的隐式类型转换,使得这种原本滋补的特性成为了一剂毒药。换句话说,C++所引入的动多态破坏了C的直观性。

舍弃之后
从上面的分析来看,动多态同C的编程模型是不相容的。因而如果希望得到C的直观性,并且消除C++的缺陷,必须放弃动多态这个特性。现在来看看放弃之后将会怎样。
一旦放弃了动多态,也就放弃了虚函数和vtable。此时,所有的对象都是pod了。那么首当其冲的好处,就是可以进行非侵入的序列化、hash计算等等操作。由于对象肯定是连续分布的,可以直接地将对象取出进行编码、存储、计算和传输,而无需了解对象内部的数据结构和含义。另外一个重要的问题也会得到解决,这就是ABI。在C中统一的ABI很自然地存在于语言中。我们可以很容易地用link将两个不同编译器编译的模块连接起来,而不会发生问题。但是,C++中做不到,除非不再使用类而使用纯C。目前C++还没有统一的ABI,即便标准委员会有意建立这样的规范,实现起来也绝非易事。但是,如果放弃动多态之后,对象的布局便回归到C的形态,从而使得ABI不再成为一个问题。
另一方面,随着动多态的取消,那么继承的作用被仅仅局限于代码复用,不再具有构造接口的作用。我们前面已经看到,继承类向基类的隐式转换,是为了使基类能够顺利地成为继承类的接口。既然放弃了动多态,那么也就无需基类再承担接口的任务。那么由继承类向基类的隐式类型转换也可以被禁止:
void func(A* a);
B b;
func(&b); //编译错误,类型不匹配
进而对象切割也不会发生:
B ba[5];
func(ba); //编译错误,类型不匹配
尽管根据数组-指针的等价性,ba可以被隐式地转换为B*,但是B*不再能够隐式地转换为A*,从而避免了对象的切割。
问题是,如此简单地将动多态放弃掉,就如同将水和孩子一起泼掉那样,实际上放弃了动多态带来的好处。实际上并非如此。我们放弃动多态这个特性,但并不打算放弃它所具有的功能,而是用另一种技术加以替代。这便是runtime concept(这里和这里)。
不同于以类型为基础的interface,concept是独立于类型的系统。concept生来便是为了描述一组类型,因而是接口最理想的实现手段。当concept runtime化之后,便具有了与动多态相同的功能(很多方面还有所超越)。
runtime concept同样需要类似vtable的函数分派表,但由于它不是类型,这些分派表无需存放在对象内部,可以独立放置(可以同RTTI信息放在一起),并且只需一份。正是基于这个特性,方才保证了所有对象依然是pod,依然能够保证对象布局的直观性。
同样,runtime concept承担了接口的任务,但不象动多态那样依赖于继承和相应的隐式类型转换。(通过自动或手动的concept_map)。因而,我们依旧可以禁止基于继承关系的隐式类型转换,从而防止对象切割的情况。
一旦使用concept作为多态的实现手段,反倒促使原本动多态的一些麻烦得到消除。在动多态中,必须指定virtual函数。如此,在一个类中会存在两种不同形态的函数,实现动多态的虚函数,和无此功能的普通函数。准确地维护这样两种函数,颇有些难度。而且,函数是虚还是不虚,牵涉到系统的设计,必须在最初构建时确定,否则以后很难修改。但在放弃动多态,使用concept的情况下,只要一个继承类中,使用相同的签名覆盖基类中的函数,便实现了多态。当进行concept_map,即将接口与类绑定时,只会考虑继承类的函数,而忽略基类中被覆盖的函数。于是,只需简单的覆盖,便实现了多态的控制。对于是否多态一个函数,即是否改变基类函数的行为,完全由继承类控制,在创建基类时不必为此伤神。其结果就是,我们无需在系统设计的最初一刻就操心多态的问题,而只需根据实现的需要随时实现。

其他

存在大量隐式转换也是C++常受人诟病的一个方面,(特别是那些Pascal系的程序员)。隐式转换的目的是带来方便,使得编码更加简洁,减少冗余。同时也使得一些技巧得以施行。但是,隐式转换的副作用也颇为可观。比如:
void fun(short a);
long a=1248;
fun(a); //顶多一个警告
这种转换存在两面性:一方面,它可能是合理的,因为尽管a类型long大于short,但很可能存放着short可容纳的数值;但另一方面,a的确存在short无法容纳的可能性,这便会造成一个非常隐蔽的bug。
C/C++对此的策略是把问题扔给程序员处理,如果有bug那是程序员的问题。这也算得上合情合理,毕竟有所得必有所失,也符合C/C++的一贯理念。但终究不是最理想的方式。但是如果象Pascal那样将类型管得很死,那么语言又会失去灵活性,使得开发的复杂性增加。
如果试图禁止隐式类型转换,那么为了维持函数使用代码的简洁性,函数必须对所有的类型执行重载。这大大增加了函数实现的负担,并且重复的代码严重违背了DRY原则。
现在或许存在一些途径,使得在维持绝对强类型的情况下获得所希望的灵活性。钥匙可能依然在concept手上。考虑如下的代码:
void fun(Integers a);
long a=1248;
fun(a);
longlong b=7243218743012;
fun(b);
此处,fun()是一个函数,它的形参是一个concept,代表了所有的整型。这样,这个函数便可以接受任何一种整型(或者具有整型行为的类型)。我们相信,在一般的应用下,任何整数都有完全相同的行为。因此,我们便可以很方便地使用Integers这个接口执行对整数的操作,而无需关心到底是什么样的整数。
如此,我们便可以在禁止隐式类型转换,不使用函数重载的情况下,完成这种函数的编写。同时可以得到更好的类型安全性。

强制类型转换是非常重要的特性,特别是在底层开发时。但也是双刃剑,往往引来很隐蔽的错误。强制类型转换很多情况下是无理的,通常都是软件的设计问题造成的。但终究还是有一些情况,需要它来处理。
设想这样一个场景:两个一模一样的类型,但它们分属不同的函数。(这种情形尽管不多见,但还是存在的。这往往是混乱设计的结果。当然也有合理的情况,比如来自两个不同库的类型)。我现在需要写一个函数,能够同时使用这两个类型。比较安全一些的,可以用函数重载。但是两个重载的函数代码是一样的,典型的冗余代码。当然也可以针对其中一个结构编写代码,然后在使用时,对另一个结构的实例执行强制类型转换。但是,强制类型转换毕竟不是件好事。因此,我们也可以构造一个concept,让它描述这两个类型。然后在编写函数时使用这个concept,当这两个类型都与concept绑定后,便可以直接使用这两个类型,而没有类型安全和代码冗余的问题。
(顺便提一下,这种方式也可以运用在类型不同的情况下。比如两个类型不完全相同,但是基本要素都一样。那么就可以使用concept_map的适配功能,将两个类型统一在一个concept下。这种方式相比oop的Adapter模式,更加简洁。adapter本身是一个container,它所实现的接口函数,都必须一一转发到内部的对象,编写起来相当繁琐。但在concept_map中,对于那些符合concept描述的函数无需另行处理,concept会自动匹配,只需对那些不符合要求的函数执行适配。)
...全文
679 65 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
65 条回复
切换为时间正序
请发表友善的回复…
发表回复
yeliguo12345 2008-08-19
  • 打赏
  • 举报
回复
看完勒,不错
longshanks 2008-08-19
  • 打赏
  • 举报
回复
csdn好歹总算恢复了。

@Kenmark:
toplanguage上出现了taodm,但不知是否同一个人,不敢随便认。
aniuman 2008-08-18
  • 打赏
  • 举报
回复
学习
Mephisto_76 2008-08-18
  • 打赏
  • 举报
回复
在C里边,宏也是有很多缺陷的。只不过大家都习惯了,不愿意去正视而已。
longshanks 2008-08-09
  • 打赏
  • 举报
回复
完美的concept机制和完美的兼容模型一样是不存在的,非鸡肋的concept模型会给C++又一次巨大的膨胀
===========================
为了避免过度膨胀,我觉得就应该C--。除了剪除动多态以外,还有隐式类型转换等等。去除这些之后,C++的很多问题和缺陷便会消除。该并的就要并,象模板特化和函数重载。
删了些东西,并了些东西,可能会使得使用上多一些麻烦,比如:
int *a=...;
if(a) ...
就应该禁止,必须写成if(a==null)...尽管多写了写代码,但是规则更少了,语言就简单了。
东西大不怕,只要东西相互并行,直白,不容易藏缺陷就好。语言复杂并不是因为功能多,而是因为规矩太多。C++的很多非本质需要的规矩是麻烦的真正根源,其中很多都和语言本身的主干特性没有关系。比如隐式类型转换,基本上就是为了少写代码,或避免重复代码而存在的。但其制造的规则和引来的麻烦,比带来的好处更多。一方面,可以少偷些懒,用显式类型转换;另一方面可以用GP消除冗余代码。
当然,减也要有个减法。象Java那样不分青红皂白,或许可以适合业务开发,系统级语言肯定不行。剪掉那些“非受惑性失误”,是应该的。而与语言能力有关的特性,则最好寻找更加完善,更加本质的机制加以替换,最好以一当十地替换。(如果是更加本质的机制,通常就可以做到这一点)。
csgdseed 2008-08-08
  • 打赏
  • 举报
回复
顶,学习
我啃 2008-08-08
  • 打赏
  • 举报
回复
Mephisto_76:
没有继承的类,或许你的意思是只要ADT不要OO即C?=ADT+GP+C
问题是GP和C是有冲突的,典型的有:#define M(a,b) a##b
M(std::vector<int,int>)
替换中std::vector<int为a,int>为b
而且对泛化后的东西,如果没有强有力的concept+trait(目前没有那么强有力)则不能对其施以C的很多操作,从而template完全退化成自动代码生成机

longshanks:
“到了二进制兼容的隘口,C++最后的花衣褪去~ ”对应ABI问题,这个不是一朝一夕的事情
C++一方面要无限接近底层,一方面要体现高级抽象,我记得Hoare有一句话很适用:
“有两种设计软件的方法:一种通过简化使它明显地没有不足,一种通过复杂化使它没有明显的不足”个人感觉C是前者。
“现有的C++允许我们触碰内存布局”这句我不敢苟同,目前的标准放任编译器商实现,没有统一化内存模型任何触碰行为都是非标准的~也可以说是非C++仅仅是C++方言的
记得我曾在cu上发过一个帖子,贴一些主流的C++眼见,同时想收集一些非主流的意见
见http://bbs.chinaunix.net/thread-1052494-1-1.html
我不喜欢过度的论战,就一些是或不是,好或不好的东西大谈阔谈,我更喜欢多看看有趣的观点,代码,思想(故我通常属于只看帖不回帖的那种家伙)~我和莫兄有很多意见相符的地方(如GP),莫兄的通俗易懂的文章系列也常能带给我启发
分享一句非主流名言:
编译器给予,编译器带走,到最后我们只是虚拟机隘口摇摆不定的比特,等待着在一个总是太慢的元处理机上实现我们那一点小小的逻辑。
P.S.少了taodm和vitin来论战似乎硝烟不能弥漫,呼唤一下
taojian_hhu 2008-08-08
  • 打赏
  • 举报
回复
顶一下,有时间学习
longshanks 2008-08-08
  • 打赏
  • 举报
回复
@liyiwen007:
文中后来反复提到的concept便是Generic Programming的最新进展。
longshanks 2008-08-08
  • 打赏
  • 举报
回复
@Kenmark:

不同标号的数据成员的布局问题是C++标准造成的,并非问题的本质,也并非做不到统一布局。这一点与vtable对内存布局的影响没有什么关系。很多语言都拥有统一的布局,C++过度地放任了编译器厂商,给予他们过分的自由。而且如果这个问题得不到解决,将来ABI的问题也很难解决。如果C++还在继续发展的话,早晚得解决这个问题。ABI这块硬石头目前似乎还没有人去啃,只看到了Vandvood的module提案,但还没有直接关于ABI的。我怀疑根据目前C++标准对布局的态度,ABI只能想想而已。
理论上,抽象后就不应该考虑内存布局。但是我们讨论的不是Pascal,或者java,而是C++,为底层和系统级开发而生的语言。在这个领域里,我们不得不触碰底层的东西,包括内存布局。现有的C++允许我们触碰内存布局,但只能在C的范畴内,而无法同时拥有高级抽象特性。我想看看是否有什么办法使得两者兼得。很明显,多数底层的开发依旧使用C,因为在它们的领域里,玩弄内存布局是最常见的操作,C++的高级特性帮不上什么忙,只增加了负担。问题在于C的开发效能低,而且错误率高。如果能够融入一些高级的抽象特性,但同时不改变其原有模型,则是一举两得的事情。在系统级的领域里,我们不能要求绝对的抽象。抽象机制的作用更多地应该是提升开发效能。纯粹的抽象,或许只有业务建模中才会有用。
实际上,统一的对象布局也并不会破坏抽象。对象布局统一,但不一定要可见。在统一布局的语言中,对于不包含资源引用的简单对象可以直接复制到另一个编译器编译的模块中,直接使用。此间,程序员无需知道对象布局,编译器清楚即可。
但目前C++在这一点上,动多态的vtable的负面作用极难消除。也就是说,在vtable存在的情况下,即便是不包含资源引用的简单对象也难以建立统一的对象模型,除非放弃对性能的要求,像java那样。即便如此,依然无法像C那样简单地处理这类对象。去除vtable,相当于降低了形成pod的要求。使得有更多地机会获得pod,同时又能获得抽象特性(通过GP,static的或者runtime的)。

我觉得C++的根本问题并非完全出在该不该兼容C上,而是如何兼容副作用才会更小。我们并不知道是否真的有一种万全的方法兼容C而不出问题。但是,现在C++的机械混合方式肯定未能达到预期的效果。或许一些新的技术和模式可以化解至少一部分的矛盾。两害相权取其轻。当然,限于C++早期的技术条件,这是唯一的做法,但现在不是了。
ateen 2008-08-08
  • 打赏
  • 举报
回复
拜读!
我啃 2008-08-08
  • 打赏
  • 举报
回复
[Quote=引用 58 楼 Mephisto_76 的回复:]
@Kenmark:
你说的GP和C不兼容的问题缺失存在。其实这不是GP的过错,而是C中宏的过错。但是鉴于宏的广泛使用,这样的问题还是需要重视的。

关于强有力的保证,concept正是我们期待的。
要说完全是代码生成机制,也不尽然。它还是做很多语法检测的,这方面个人认为它是优于宏的。
[/Quote]
本没有谁的过错,宏在C里活的好好的,C++加个GP反而宏变得不是了?
完美的concept机制和完美的兼容模型一样是不存在的,非鸡肋的concept模型会给C++又一次巨大的膨胀
老实说充当 代码生成机制的话,GP也不合格,它检测失败后的错误信息是给火星人看的,扯远了~
Mephisto_76 2008-08-08
  • 打赏
  • 举报
回复
@Kenmark:
你说的GP和C不兼容的问题缺失存在。其实这不是GP的过错,而是C中宏的过错。但是鉴于宏的广泛使用,这样的问题还是需要重视的。

关于强有力的保证,concept正是我们期待的。
要说完全是代码生成机制,也不尽然。它还是做很多语法检测的,这方面个人认为它是优于宏的。
lin_style 2008-08-08
  • 打赏
  • 举报
回复


现在不可能一门语言打拼了。

有点缺陷反而是有利

Kenmark在CU的帖子里不是有人说要相信程序员的能力 = =!


如果LZ能拿另一门语言进行对比会更直观点。
lin_style 2008-08-08
  • 打赏
  • 举报
回复
现在编码都是
C++做骨架和皮肉
C做内脏
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 lingol 的回复:]
好贴留名
[/Quote]
SenerityChzr 2008-08-08
  • 打赏
  • 举报
回复
mark,下去再看……
Chendy1985 2008-08-08
  • 打赏
  • 举报
回复
upup
太乙 2008-08-08
  • 打赏
  • 举报
回复
just mark!
n_yHHy_n 2008-08-08
  • 打赏
  • 举报
回复
学习!
加载更多回复(45)

65,187

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧