huaxiazhihuo

 

难以割舍的二段构造

        两段构造也是声名狼藉得很,比之于MFC,好不了多少,貌似MFC中到处都是两段构造,难道两段构造的声誉也是受MFC所累。定义完了一个对象变量之后,还要再调用一次该对象的Create函数,而且还要Create成功了之后,才能对该对象做进一步的操作,否则对象将一直处于非法状态。这种代码方式写起来确实很恶心,为何不直接在构造函数中直接Create,不成功就抛出异常,然后对象就流产了,好过它半死不活地一直苟延残喘于世上,累己累人。其实,MFC选择两段构造也是有苦衷:1、先是很久很久以前,VC编译器对异常的支持不怎么好,当然,现在的VC编译器,自然今时不比往日,但是,还要兼容以往的代码;2、然后是MFC的设计,它只是对API做了一层薄薄的包装,薄薄的意思,就是,不管怎么捣鼓,都难以将WINDOWS系统中的各种对象包装成一个干净的C++对象了,因为,API本身就采用两段构造。可不是吗?定义一个句柄变量,然后CreateXXX返回结果,返回值非法,表示创建失败。失败了,还要霸王硬上弓,后果会怎么样,这谁也不知道。
        理论上,构造函数抛出异常确实很优雅,代码也更具美感,并且,其行为也更加明确,要么就处理,要么,就等着程序异常退出。但是,实际上,异常这种东西,真正实现执行起来,却相当的困难。更何况,如果完全丢弃两段法,除了异常,还会引入一些新的问题,正所谓:“前门驱虎,后门进狼”,进来不只是一只狼,而是好几只。生活的奥妙,就在于制造出新的问题,以解决旧的问题。
        构造函数中直接调用Create,就表示了用户一定义一个类型变量,程序就会马上启动Create函数,也就意味着可能将创建窗口对象、内核对象、甚至启动新的线程等等,这些操作都不是省油的灯,构造函数中做了太多事情,会有隐藏太多细节之嫌,代码本来就是为了隐藏细节,这个多事之罪名暂且不论;但是,用户没法对创建过程Say NOT,也即是说,用户一定义对象变量,就只能接受它的高昂的创建过程。难道,一开始就让对象进入有效状态,这都有错吗?确实是的。有时候,用户只是先想声明(定义)对象,等必要(时机成熟)的时候,再让它进入有效状态。咦,用户这样写代码,不太好吧,应该强制他/她等到了那个时候,再定义对象变量。变量怎么可以随随便便就定义呢?应该在要使用的时候,才定义它,这才是良好的代码风格。但是,有些情况,确实需要先暂时定义非法状态下的对象变量,比如,这个对象是另一个对象(拥有者)的成员变量时,那也没什么,强制用户在必要的时候,才定义拥有者对象变量。但是,假如这个拥有者必须是全局变量,那该怎么办?那也没什么,将拥有者定义为指针变量就是了?好了,本来只是要对象创建失败的情况,现在还要考虑内存分配的细节,然后接着就是new delete,然后就是各种智能指针闪亮登台演出,更糟糕的是,对象有效无效的问题依然没有根除,因为,只要引入指针,每次使用指针,就必须检查指针是否有效,咦,难道操作空指针不会抛出异常吗?C++规范中,操作空指针属后果未确定的行为,对C++而言,未确定往往就是最糟糕的意思。此外,鉴于对象只能一直处于有效状态,它就不可能提供让对象进入无效状态的操作。如果想要让对象无效,唯一的办法,就是让它死去,强制对象启动析构函数,方法是离开作用域强者delete它。下次要使用它的时候,就再new一次或者定义一次,不,它已经是另外一条新生命了。但是,对于两段构造的对象,只须Destroy又或者Create,对象可以永远只有一个。此外,二段构造颇具扩展性,很轻易地就可搞成三段构造,每一步,用户都有选择的权利。但构造异常就没有这个优点。
        考虑到构造函数中的参数问题,比如,月份的参数,大家都知道,有效值只在1-12月之间。不讨论这种情况下,非法的参数传递是否属于代码的逻辑问题。对此,构造异常指导下的对象是不可能出现无参(没有参数或者参数都有缺省值)的构造函数,因此,它们也都不能用于数组,难以应用于全局变量、静态变量、作为其他对象的数据成员,如果非要在这些场合下使用它们,比如占位符的作用,唯有用上指针,于是伴随而来的,又如上文所述,使用指针之前,必须检查指针的有效性,只怕不会比检查二段构造的有效性好多少。
        二段构造不轻易剥夺用户的权利,提供更多选择,可用于数组、堆栈、STL中的容器,要它死,它就死,要它活,它就活,但是,它可以从来都未曾消失过,要做的,仅仅是在使用它时,清楚它是死是活就行了,不过多加几次判断而已。相比之下,构造异常就更具侵入性了,一旦用上,就只能被迫遵照它的规则行事。
        其实,两段构造与构造异常,都很恶心,只要一处代码中用到了它,所有与之相关的代码都没法脱身。差别不过在于谁比谁恶心而已,这个,视各人的口味而不同。对于本人这种害怕分配内存,释放内存,更加畏惧异常的人来说(这并不表示本人写不出异常安全的代码),当然优先选择二段构造,MORE EFFECTIVE的条款中,声称,如无必要,不要提供缺省的构造函数,以免对象陷入半死不活的状态中。而我的习惯作法则是,如无必要,必须提供缺省的构造函数,不要轻易剥夺用户想要使用对象数组的权利,或者是由于不提供缺省的构造函数,而由此引起的种种不便。
        好了,既然程序中决定用二段构造了,那么,假如用户定义了一个对象,忘了再构造一次,但是又要执行其他操作,怎么办?嗯,那也没什么,既然用户不遵守契约,我们的对象自然可以做出种种不确定的行为。当然,别忘了,在其他的每一个操作上都添加几条assert语句,尽管这很恶心,也聊胜于无,减少点罪恶感,以便于在调试版中找出问题。

posted on 2012-06-14 15:08 华夏之火 阅读(3717) 评论(14)  编辑 收藏 引用 所属分类: c++技术探讨

评论

# re: 难以割舍的二段构造 2012-06-14 16:37 Richard Wei

支持一下,C++里我们一般把分配内存和简单的初始化工作都放在了构造函数里,而把一些复杂的工作放在一个单独的初始化函数里,这样的话比较灵活。如果觉得使用不方便,可以自己封装一层,也就是所谓的RAII了。  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-14 17:45 华夏之火

是啊,在下也偏向于精简构造函数。对于多事的构造函数的代码,内心总是很排斥,本能的对于细节的感兴趣,或者这也是C++者们的通病@Richard Wei
  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-14 18:35 春秋十二月

http://www.cppblog.com/qinqing1984/archive/2011/07/04/150084.html  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-14 20:41 华夏之火

?这些都是深度探索c++对象模型中的内容,每一个想在c++上有所作为的人必须先深度探索这本书。@春秋十二月  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-15 11:25 guilin

有时候,用户只是先想声明(定义)对象,等必要(时机成熟)的时候,再让它进入有效状态
这是你不用异常的基础论点。绝对不应该出现这种用法,所以整篇文章都是扯淡  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-15 11:33 guilin


“有时候,用户只是先想声明(定义)对象,等必要(时机成熟)的时候,再让它进入有效状态。”
这正是二段构造的用法。
你说构造+异常的用法不好的理由却是:“有时想使用二段构造。”……
这就像为什么不开车:因为有时候想走路,所以开车不好。  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-15 11:42 华夏之火

在下也知道不应该这样用,只是碰到这样的需求,该怎么办,难道要用指针吗?@guilin
  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-15 16:44 guilin

@华夏之火
我从来没碰到这样的需求,你可以举个简单例子出来看看  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-15 17:13 华夏之火

好比一个服务器类,我们一般都是先定义一个对象变量,然后让变量开始监听端口并启动监听的线程,这也可以看成二段构造吧。难道你要定义服务器对象的时候,就让它在构造函数里面直接就开始监听并启动线程吗@guilin
  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-17 17:57 guilin

@华夏之火
这很正常,可以看看boost asio的几个例子,都是在构造函数中干的  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-17 18:00 guilin

@华夏之火
类成员对象初始化在初始化列表中已经初始化完成了。C++构造函数其实本质上就是一个init函数,只不过没有返回值。有了异常,也算有返回值了。所以再多写一个init函数实在没有必要  回复  更多评论   

# re: 难以割舍的二段构造 2012-06-18 08:59 华夏之火

受教了,可能是在下内心对于异常的排斥,所以总是要尽量避免这个东西@guilin
  回复  更多评论   

# re: 难以割舍的二段构造 2012-07-09 16:12 哥没注册

纠结了
关于C++从C上扩展出来的这类功能。有益则用,无益就不理会。比如鄙人基本不使用NEW/DELETE,也就没有你的困惑了。HOHO~~  回复  更多评论   

# re: 难以割舍的二段构造 2012-07-09 17:11 华夏之火

NEW/DELETE的问题不大,主要是某些较耗资源的玩意,好比线程、文件、数据库连接,直接在构造函数中启动,会让人很纠结,觉得隐藏太多细节@哥没注册
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(6)

随笔分类

随笔档案

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜