Visual C++中4355的警告(warning C4355: 'this' : used in base member initializer list),字面意思是this指针用在类成员初始化列表中。这个警告是怎么出现的呢?通过下面这个程序来说明:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#include
<exception> #include <iostream> class Base { public : explicit Base() { } virtual void init() = 0 ; virtual void uninit() = 0 ; }; class Strategy { public : explicit Strategy( Base *p ) : m_p( p ) { m_p->init(); } ~Strategy() { m_p->uninit(); } private : Base *m_p; }; class Derived : public Base { public : explicit Derived() : m_instance( this ) , pi( 0 ) { throw std::exception( "The exception happened in the constructor of class Derived. " ); pi = new int ( 0 ); } ~Derived() { delete pi; } virtual void init() { } virtual void uninit() { *pi = 500 ; } private : Strategy m_instance; int *pi; }; int main( int argc, char *argv[] ) { try { Derived temp; } // try catch ( std::exception &e ) { std::cout << e.what() << "\n" ; } catch ( ... ) { std::cout << "Unknown exception has been caught! \n" ; } // catch return ( 0 ); } |
生成时编译器会报出4355警告。这个警告定位在Derived构造函数的成员初始化列表中,准确地说是m_instance( this )这里。那么这个警告的意义是什么呢?运行一下就知道——程序挂了。问题出在Derived::uninit方法,它在main函数结束时销毁temp对象调用析构函数后被调用,而其pi成员是个NULL,引用它的地址进行赋值操作当然就挂了。原因显而易见:Derived构造函数没执行完就异常退出,pi成员没有执行分配内存操作。也就是说,在构造函数执行结束前发生异常,那么这个对象就是不完整的,如果用成员初始化列表的方式把这个对象的this指针拿去构造另一个对象就会有安全隐患,因为后者也不是一个完整的对象。
我是在ACE(5.6)一个Reactor示例中发现的这个问题,上例中的Strategy和Base分别模拟ACE_Reactor_Notification_Strategy和ACE_Svc_Handler。库的作者设计了抽象类和策略类之间的构造关系,使用者在实现时常会出现这个问题。解决方法一:把Derived中的Strategy成员改为指针成员,然后在初始化方法中用this指针创建它的实例即可。优点是确保安全且无编译警告,缺点是使用时多了一个初始化调用操作。解决方法二:也是使用Strategy指针成员,但其对象实例的创建放在构造函数最后,并确保之前的构造调用无异常或异常安全。优点正是避免了上一种解决方法的缺点,而缺点就是仍然有编译警告。