今天在用C++写与Mysql数据库交互的时候碰到一个问题,代码如下:
class Mysql { private: string Host; string User; string Password; string Database; MYSQL* mysql; public: Mysql() { mysql_init(mysql);//A } Mysql( const string& host , const string& user , const string& password , const string& database ) : Host(host) , User(user) , Password(password) , Database(database) { Mysql();//B } void open() { mysql_real_connect( mysql , Host.c_str() , User.c_str() , Password.c_str() , Database.c_str() , 3306 , NULL , 0 );//C } //………… };
int main() { Mysql* m = new Mysql( "127.0.0.1" , "root" , "123456" , "test" ); m->open(); }
Mysql类有两个构造函数,默认构造函数在A处初始化了mysql这个私有变量,这样以后在C处才能连接数据库。同时另外一个构造函数有四个string参数,用来提供连接数据库时所需要的信息,为了节省代码量,在B处显式调用了默认构造函数,想以此来完成对mysql变量的初始化。
实际运行时发现,程序在执行到mysql_real_connect这个函数时报错退出,经过调试后发现即便调用了第二个构造函数对mysql初始化,在程序运行到C这里时,mysql变量的地址仍为0,这样当然无法让mysql_real_connect成功连接到数据库。
经过在网上查找,发现问题在于B处的构造函数中调用了另外一个构造函数。
我们知道,当定义一个对象时,会按顺序做2件事情:
1)分配好内存(非静态数据成员是未初始化的)
2)调用构造函数(构造函数的本意就是初始化非静态数据成员)
在上面代码中,主函数main第一句声明并定义了指针变量m,这里已经为m分配了内存,然后调用带参数的构造函数,但是构造函数还未执行完,却调用了默认构造函数,这样相当于产生了一个匿名的临时Mysql对象,它调用Mysql()构造函数,将这个匿名临时对象自己的数据成员mysql进行了初始化操作;但是m中的mysql并没有得到初始化,因此其值mysql=0x0。
网上看到的归纳如下:
1)在c++里,由于构造函数允许有默认参数,使得这种构造函数调用构造函数来重用代码的需求大为减少
2)如果仅仅为了一个构造函数重用另一个构造函数的代码,那么完全可以把构造函数中的公共部分抽取出来定义一个成员函数(推荐为private),然后在每个需要这个代码的构造函数中调用该函数即可
3)偶尔我们还是希望在类的构造函数里调用另一个构造函数,可以按下面方式做:在构造函数里调用另一个构造函数的关键是让第二个构造函数在第一次分配好的内存上执行,而不是分配新的内存,这个可以用标准库的placement new做到:
标准库中placement new的定义如下:
inline void *__cdecl operator new( size_t , void *_P ) { return (_P); }
可见没有分配新的内存。
于是可以这样做:
class Mysql { private: string Host; string User; string Password; string Database; MYSQL* mysql; public: Mysql() { mysql_init(mysql); } Mysql( const string& host , const string& user , const string& password , const string& database ) : Host(host) , User(user) , Password(password) , Database(database) { new (this)Mysql(); } void open() { mysql_real_connect( mysql , Host.c_str() , User.c_str() , Password.c_str() , Database.c_str() , 3306 , NULL , 0 ); } //………… };