一道面试题,答案可能不惟一:
C++构造函数无返回值,如何判断对象是否“构造”成功?
注意:这里的“构造”不是单指分配对象本身的内存,而是指在建立对象时做的初始化操作(如打开文件、连接数据库等)
这就是为什么我们在创建对象的时候要用:
try, catch的原因。
这种类似作业的构造函数的创建,一般会有异常处理机制,在可能失败的地方抛出异常,在外面catch处理就可以了。
当然用异常抛出是最好的
添加表示初始化成功与否的成员函数,当程序自动调用构造函数时,调用该成员函数判断“构造”是否成功
用bool变量,理论是是不可行,有漏洞的。
假如在一个构造函数里面构造多个对象,当构造一个成功的时候,
另外一个失败,在这个失败的对象里面跑出异常,此刻前面那个对象因没有释放,而
内存泄漏,bool可能根本就走不到哪儿去。
构造函数中抛出异常时概括性总结
(1) C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常;
(2) 构造函数中抛出异常将导致对象的析构函数不被执行;
(3) 当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构;
书上是指出最好不在构造函数抛出异常。
构造其他对象,完全可以用其他函数代替实现。
可以直接用初始化的方式构造其他对象,这样就不需要抛出异常了。
而对象A的成功实际关于对象B是否成功。
A的失败必然导致B的失败。那么在使用B时,就可以直接判断了。
class B{
B(): a(new A){}
A* a;
};
本人看到的可靠方式,也就是下面一条:将构造与init函数封装在create函数中,处理失败与否,就很容易处理了,因为init中做了大多的初始化工作。
如果这样的话,不如将构造函数设为空。
增加bool Initial()成员函数。
通常在构造函数中增加复杂的逻辑是不合适的。
C#/java 中抛异常似乎是出错处理的标准做法,而在 C++ 中却是应该尽量避免的
我们原来的项目中,很多构造函数都是空的,
这部分功能完全可以用initial和SetXXX等函数代替,这样就可以处理出错的情况
参考symbian c++的设计规范吧~!两阶段构造(转载一篇如下)!????
————————————————————————————————————————————————————————————————
3.6 两阶段构造
考虑如下代码,它为CExample类的对象在堆内存上分配了空间,并相应地将其赋给foo:
CExample* foo = new(ELeave) CExample();
代码调用new操作符来在堆内存拥有足够空闲空间的时候为CExample对象分配内存。完成分配以后,调用CExample的构造函数来初始化对象。但是如果CExample的构造函数异常退出的话,已经为foo分配的内存空间以及构造函数中可能分配的额外内存都将被孤立。
这就引出了Symbian OS中一个基本的内存管理规则:C++的构造函数永远也不能异常退出。
但是,通常可能需要编写一些异常退出的初始化代码,比如为存储另一个对象而分配内存,或者从一个可能已经不存在或已经毁坏的配置文件中读取信息。有很多初始化可能会失败的原因,在Symbian OS中相应的解决方法是用两阶段构造。
两阶段构造把对象的创建分成了两个部分或阶段:
1. 一个不能异常退出的C++构造函数
这个构造函数会被new操作符调用。它显式地调用基类的构造函数,并且可以调用那些不能异常退出的函数,和/或用默认值或构造函数的参数来初始化成员变量。
2. 一个单独的初始化方法(典型的名称是ConstructL())
这个方法可以在分配了内存并且new操作符所创建的对象被推入清除栈时被调用;它将完成对象的创建工作,并以安全的方式执行可能异常退出的操作。如果出现一个异常退出,清除栈会调用析构函数来释放那些已经成功分配的资源,以及为此对象本身所分配的内存。
一个类通常会提供一个将两个构造阶段都包含进来的公共静态函数,并为此函数取个简单明了的能表达初始化含义的名称(这样那两个用于构造的方法就可以声明成私有成员或保护成员,以免被不小心误用了)。这种工厂函数通常被称作NewL(),它是一个静态方法,这样就可以在拥有一个此类的实例之前得以被调用。比如:
class CExample : public CBase { public: static CExample* NewL(); static CExample* NewLC(); ~CExample(); // Must cope with partially constructed objects ... // Other public methods, e.g. Foo(), Bar() private: CExample(); // Guaranteed not to leave void ConstructL(); // Second-phase construction code, may leave CPointer* iPointer; };
注意:类CExample也有一个NewLC()方法。此方法也是一个工厂函数,但它执行后是在其返回的清除栈上留有一个指针。
提示
如果在函数返回时,对象的指针已经被推入清除栈,并依然保留在其中,那么按照Symbian OS习俗,这个函数名后面应该加字母C。这样做就暗示了调用者,如果此函数被正确返回,那么清除栈上还有一个额外的指针。
让我们看看典型的NewL()以及NewLC()的实现:
CExample* CExample::NewLC() { CExample* me = new (ELeave) CExample(); // First-phase construction CleanupStack::PushL(me); me->ConstructL(); // Second-phase construction return (me); } CExample* CExample::NewL() { CExample* me = CExample::NewLC(); CleanupStack::Pop(me); return (me); }
NewL()工厂函数是用NewLC()函数而不是其他函数(因为需要一个额外的PushL()调用来把指针推入清除栈,所以效率较低)来实现的。
两个工厂函数要么都返回一个完全构造的对象,要么由于没有足够的内存供对象分配(也就是new(ELeave)异常退出)或第二阶段的ConstructL()函数由于某种原因而异常退出。这也就意味着,如果一个对象通过两阶段构造被完全初始化,那么类的实现中就不再需要在每次使用成员变量前都检查它们是否合法了。换言之,就是如果一个对象存在,那么它必然已被完全构造出来了。这样做就得到一种高效的类实现,使得不需要在取消指针的引用之前对每个成员指针进行测试。