01  

包含成员变量的类,须定义构造函数或者默认构造函数

说明:如果类有成员变量,没有定义构造函数,又没有定义默认构造函数,编译器将自动生成一个构造函数但编译器生成的构造函数并不会对成员变量进行初始化,对象状态处于一种不确定性。

例外:如果这个类是从另一个类继承下来,且没有增加成员变量,则不用提供默认构造函数。

示例:如下代码没有构造函数,私有数据成员无法初始化:

    class CMessage public: void ProcessOutMsg() //… private: unsigned int msgid; unsigned int msglen; unsigned char *msgbuffer; }; CMessage msg; //msg成员变量没有初始化 msg.ProcessOutMsg(); //后续使用存在隐患//因此,有必要定义默认构造函数,如下:class CMessage public: CMessage (): msgid(0), msglen (0), msgbuffer (NULL) //... };

    02  

    为避免隐式转换,将单参数构造函数声明为 explicit

    说明:单参数构造函数如果没有用 explict 声明,则会成为隐式转换函数

    示例:

      public: explicit Foo(const string &name):m_name(name) private: string m_name; }; ProcessFoo("zhangsan"); //函数调用时,编译器报错,因为显式禁止隐式转换

      定义了 Foo::Foo(string &name),当形参是 Foo 对象实参为字符串时,构造函数 Foo::Foo(string &name)被调用并将该字符串转换成一个 Foo 临时对象传给调用函数,可能导致非预期的隐式转换。

      解决办法:在构造函数前加上 explicit 限制隐式转换。

      03  

      包含资源管理的类应自定义拷贝构造函数、赋值操作符和析构函数

      说明:如果用户不定义,编译器默认会生成拷贝构造函数、赋值操作符和析构函数。

      自动生成的拷贝构造函数、赋值操作符只是将所有源对象的成员简单赋值给目的对象,即浅拷贝(shallow copy);自动生成析构函数是空的。这对于包含资源管理的类来说是不够的:比如从堆中申请的资源,浅拷贝会使得源对象和目的对象的成员指向同一内存,会导致资源重复释放。空的析构函数不会释放已申请内存。

      如果不需要拷贝构造函数和赋值操作符,可以声明为 private 属性,让它们失效。

      示例:如果结构或对象中包含指针,定义自己的拷贝构造函数和赋值操作符以避免野指针。

        class GIDArr public: GIDArr()   iNum = 0;   pGid = NULL; ~GIDArr()   if (pGid)   {     delete [] pGid;    }  private:   int iNum;   char *pGid;   GIDArr(const GIDArr& rhs);   GIDArr& operator = (const GIDArr& rhs); };

        04  

        让 operator=返回*this 的引用

        说明:符合连续赋值的常见用法和习惯。

          String& String::operator=(const String& rhs) //... return *this; //返回左边的对象string w, x, y, z; w = x = y = z = "Hello";

          05  

          在 operator=中检查给自己赋值的情况

          说明:自己给自己赋值和普通赋值有很多不同,若不防范会出问题。

          示例:

            class String public: String(const char *value); ~String(); String& operator=(const String& rhs); private: char *data; }; //自赋值,合法String a; a=a; //不好的例子:忽略了给自己赋值的情况,导致访问野指针String& String::operator=(const String& rhs) delete [] data; //删除data //分配新内存,将rhs的值拷贝给它data = new char[strlen(rhs.data) + 1]; //rhs.data已经删除,变成野指针strcpy(data, rhs.data); return *this; }//好的例子:检查给自己赋值的情况String& String::operator=(const String& rhs) if(this != &rhs) delete [] data; data = new char[strlen(rhs.data) + 1]; strcpy(data, rhs.data); return *this; }

            在拷贝构造函数、赋值操作符中对所有数据成员赋值

            说明:确保构造函数、赋值操作符的对象完整性,避免初始化不完全。

            06  

            通过基类指针来执行删除操作时,基类的析构函数设为公有且虚拟的

            说明:只有基类析构函数是虚拟的,才能保证派生类的析构函数被调用。

            示例:基类定义中无虚析构函数导致的内存泄漏。

              //如下平台定义了基类A,完成获取版本号的功能。class A public: virtual std::string getVersion()=0; }; //产品派生类B,实现其具体功能,其定义如下:class B:public A public: B() cout<<"B()"<<endl; m_int = new int [100]; ~B() cout<<"~B()"<<endl; delete [] m_int; std::string getVersion(){ return std::string("hello!");} private: int *m_int; }; //模拟该接口的调用代码如下:int main(int argc, char* args[]) A *p = new B(); delete p; return 0; }

              派生类 B 虽然在析构函数中进行了资源清理,但不幸的是该派生类析构函数永远不会被调用。由于基类A 没有定义析构函数,更没有定义虚析构函数,当对象被销毁时,只会调用系统默认的析构函数,故导致内存泄漏。

              07  

              避免在构造函数和析构函数中调用虚函数

              说明:在构造函数和析构函数中调用虚函数,会导致未定义的行为。在 C++中,一个基类一次只构造一个完整的对象。

              示例:类 BaseA 是基类,DeriveB 是派生类

                class BaseA //基类BaseA public: BaseA(); virtual void log() const=0; //不同的派生类调用不同的日志文件}; BaseA::BaseA() //基类构造函数log(); //调用虚函数log  }class DeriveB:public BaseA //派生类public: virtual void log() const; };

                当执行如下语句:

                DeriveB B;会先执行 DeriveB 的构造函数,但首先调用 BaseA 的构造函数,由于 BaseA 的构造函数调用虚函数 log,此时 log 还是基类的版本,只有基类构造完成后,才会完成派生类的构造,从而导致未定义的行为。同样的道理也适用于析构函数。

                08  

                拷贝构造函数和赋值操作符的参数定义成 const 引用类型

                说明:拷贝构造函数和赋值操作符不可以改变它所引用的对象。

                更多推荐