C++/CLI中新推出的自动确定性资源回收(Automatic deterministic destruction)被视为一个优秀的设计。是使用所谓C++/CLI这个“新瓶”来装Bjarne Stroustrup提出的RAII这个“旧酒”。
这的确不错,相对而言,这个比C#中的using 关键字(dispose模式),以及Java中的hard-coded的dispose方法都要好许多。这个特性是由C++/CLI中栈对象(局部对象)来提供的,局部对象本身没错,RAII也是局部对象应有之义。
但问题在于C++/CLI中栈对象的可用性由于许多原因会大打折扣,使用起来已经远远不如ISO-C++中那样流畅。下面列出了损伤其可用性的几大硬伤:
#1。C++/CLI的栈对象并非真的位于栈中
只要类型是ref class,C++/CLI中的栈对象就仍位于托管堆中。仍然使用newobj IL指令来分配。如果R没有定义析构器(~R)(注意:C++/CLI中的析构器和C#中的析构器完全两回事),那么下面两行代码实际上将生成完全一样的IL代码:
R r;
R h=gcnew R;
好像记得Herb Sutter曾经说过他们将来可能会在真正的方法栈中分配r ——说实话恐怕只有C++背景的人敢这么“胡思乱想”:) 他们现在只是想在语法层面让程序员"感觉"就像r是从栈中分配的一样。又一个syntax sugar:)
当然为了对称和语义的完美,有时候还需要在r上应用%——虽然背后仍是什么也没做:)
#2。C++/CLI编译器默认情况下不会自动产生拷贝构造函数和拷贝赋值操作符
这一点非常令人烦恼,几乎让人“望栈对象而却步”。更糟糕的是BCL中的所有类型都没有提供拷贝构造函数和拷贝赋值操作符——因为恐怕只有C++/CLI会用到他们。
话说回来,即使C++/CLI会自动产生拷贝构造函数和拷贝赋值操作符,那么继承自BCL的类型还是会很麻烦。
#3。如果函数要被其他CLI语言调用,那么就不能将其参数设计为栈对象
a. static void add(R r){...}
编译出来有一个modopt元数据,所以可以被其他语言调用,但是如果被其他语言调用,比如C#,那么其他语言将是以传值的方式传递引用,而C++/CLI将是传递对象拷贝(要调用拷贝构造器),所以语义混乱,完全不可以这样做。
b. static void add(R% r){...}
由于编译出来都有一个modreq元数据,所以不能被其他CLI语言调用。
#4。如果函数要被其他CLI语言调用,那么也不能将其返回值设计为栈对象
a. static R add(){...}
b. static R% add(){...}
两者编译出来都有一个modreq元数据,所以都不能被其他CLI语言调用。
#5。使用BCL时,如果要传递栈对象,总要使用“莫名其妙”的%操作符
比如:
String s("abc");
ArrayList list;
list.Add(%s);
实在很不好,还是使用追踪引用比较好:
String^ s="abc";
ArrayList^ list=gcnew ArrayList();
list->Add(s);
总结一下:
#1和#5对栈对象的可用性影响不算大,毕竟从语义层面来理解,还是行得通的。
但是,#2、#3、#4的影响就很大。#3和#4使得我们必须放弃使用栈对象来进行互操作。而#2会让编写C++/CLI代码非常的不方便——除非你以后不想使用栈对象。
现在的问题是,是否C++/CLI中的栈对象只是为了获得自动确定性资源回收而存在?值得这样做吗?
打印 | 张贴于 2005-01-10 23:25:00 | Tag:C++/CLI

留言反馈
[url=http://sh.db-recovery.com]上海数据恢复[/url]
ref class 在本地堆或class在托管堆,如下8个函数还是需要区分的:
void f(R*);
void f(R^);
void f(N*);
void f(N^);
void f(R&);
void f(R%);
void f(N&);
void f(N%);
还有根据C++传统,函数重载是不根据返回类型的,所以需要gcnew来支持如下:
R* r = new R;
R^ r = gcnew R;
N* n = new N;
N^ n = gcnew N;
如果gcnew出来的直接用 . 不用 ->, 这是java/C#习惯,
C++程序员肯定不习惯。托管对象也不需要delete, 如果需要确定析构,可以用栈语义。
2、3,因为c++/cli 可以同时控制本地堆与托管堆,为了不引起混乱,就多加了几个关键字和运算符,这没什么不可理解的
第一: 就是引用类型和值类型的问题,value class,ref class,真是又臭又长。
第二:就是引用类型的问题,原来C++new的指针是要用delete来释放的,所以MC++的托管对象一直因为new了没有delete而饱受争议。但是原来C++中就有引用这一类型,为什么C++/CLI中不拓展这个概念,用gcnew生成一个引用类型?这样的话,首先从语法上讲要好看许多(-> 和.,你说哪个跟好看呢?)
第三:就是托管指针的符号^以及类似的%,及其别扭的符号。使用&要习惯的多,而且好看的多。还可以更好的向ISO C++ 移植。
这种设计竟然也有一堆人说好,真是不知道是出于一种什么样的目的。难怪Bjarne Stroustrup对C++/CLI的设计也颇有微词了(原语:而C++/CLI让我来设计大概也不会是这样的)。
C# 中可以这样写:
using(IDisposable A = New TypeA(...),
B = New TypeB(...) )
{
(A as TypeA)...
(B as TypeB)...
}
不过每次 cast 都要加一个 isinst 指令,其他就和嵌套的 using 没有区别了。。。不知道 VB.net 那种写法编译成MSIL之后有什么区别。难道是在finally块中集体Dispose吗?
Using A As New TypeA, B As New TypeB, C ..
End Using
我常常这样写的
Using r As New StreamReader(..), w As New StreamWriter(..)
'work with r and w
End Using
你指的是这样吗?
using (Font MyFont = new Font("Arial", 10.0f), MyFont2 = new Font("Arial", 10.0f))
{
。。。
}
C#也可以啊。
C++/CLI写多个栈变量照样会导致程序中很多try/finally。但是这不是问题,因为在没有异常抛出的时候,代码有没有try/finally效率是一样的。
C++/CLI在这个问题上唯一胜出的地方是:
1。简单定义~R和!R就可以了,不用再自己实现复杂的Dispose模式。
2。使用栈变量语法更简洁——但是生成的代码和C#的using没有什么区别。
我已经委托宝玉为我们更新程序了,很快就出来了:)
“Java已经到头了”这句话一来是在一定语境里说的,二来我感觉翻译有点过了,或者说翻译的不够好。我想Java一直在发展,这是大家有目共睹的。说此话不过是从纯计算机语言科学的角度来说而已——比如,你也可以说“C语言已经到头了”,因为这个语言的边界已经相当清晰,它能做什么,不能做什么,已经基本上定死了,它向前发展只不过是库的累积和效率层面的优化。说这话并不表示C语言没用了,没有用武之地了。
Stan Lippman先生不是轻狂之人,他不会对Java做轻蔑的攻击。就我和他的接触过程中的感受而言,他说的有关专业领域的话都是经过深思熟虑后才说的。“Java已经到头了”这句话在中文里确实有“轻蔑”之意,所以我说它的翻译不够好,挑起了大家的情绪,却没有表达清楚Stan Lippman的意思。比如换一种翻译的方法“Java作为一门编程语言,它的核心框架已经基本定型了,不会再有大的突破”。我想这和你说的“Java的单纯稳定的语言核心”没有太大的区别。
当然这是采访实录,要所有的翻译都那么严谨并不现实——从英文到中文的转换过程中,丢失一些信息非常正常,何况这是口语交流——因为口语通常都是说个大概,不会像写文章一样写的一板一眼。中文口语如此,英文口语也如此。
我也喜欢栈语法。问题就在于栈对象的copy语义被BCL的设计给葬送了——这使得现在的C++/CLI编译器无法象ISO-C++那样产生默认的拷贝构造函数和拷贝赋值操作符。不过这怨不得C++/CLI,因为那时候C++/CLI还没有出生。
这样以来我们要避免使用栈对象来做参数,返回值,避免对其进行copy,assign。最后导致的结果就是,如你所言——只能在需要dispose的地方使用栈对象。
To rIPPER,
这方面C#的哲学就很好,用清晰的语法来表达你的语义。所以我相信C#仍旧是一个相当优秀的语言。
autodispose_handle<T>这个想法不错,但是这种表示法不好,这使得autodispose_handle<T>从形式上又成为一种新的类型——从某种角度来看又是栈对象的一个“马甲”。
我猜你的意思可能是加一个“修饰符”,比如:
autodispose_handle SqlConnection^ sqlConnection=gcnew SqlConnection();
然后在变量作用范围结束的时候,来自动调用sqlConnection的Dispose。
实际上这已经很接近C#的using了——但还是比C#的using好一点点:)
C++/CLI阵营的人曾经指责C#的using,原因是对于多个需要cleanup的对象,嵌套using效率不高,且搞的代码很乱——主要就是那个代码块{...}在搞怪。
这是让我回忆起C++时就感到头晕的原因之一 :P
我觉得语言设计的一个重要考虑就是保持语义和传统用户的习惯一致。重用某些现存的语言元素,根据上下文决定其行为是一个很好的做法;就像static关键字的多种用途一样。
开心,求你花一个周末下午,从dudu那里借鉴一个验证码程序过来吧……
其实我觉得增加一个在初始化时的识别符也可实现相关的操作。比如设计一个autodispose_handle类型,并规定用其定义的变量必须立刻初始化。并让autodispose_handle<T>可以进行所有T^能进行的操作
autodispose_handle<ArrayList> al = gcnew ArrayList()
这样,某些用DirectXX或COM程序设计时,用工厂生成的实例也可以用这种语法自动回收了。