Item5 Know what functions C++ silently writes and calls.
编译器可为一个类默认生成默认构造函数、拷贝构造函数和赋值运算符以及析构函数。注意有指针成员的情况下,默认生成的拷贝构造函数和赋值运算符只是简单的拷贝指针的值,指针指向的数据并不拷贝,即执行的为“浅拷贝”。有可能造成指针指向一个已经释放的内存位置。顾在这样的情况下最好自己实现拷贝构造函数、赋值运算符和析构函数。
Item6 Explicitly disallow the use of compiler-generated functions you do not want.
只要自己定义了Item5照哦给你提到的几个函数,编译器便不再默认生成。如果想阻止对象的拷贝和赋值,可将相应的成员函数声明为private并且不予实现。
如下:
class HomeForSale
{
public:
HomeForSale() { }
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
};
或者写一个基类,专门组织拷贝动作
如下:
class Uncopyable
{
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale : private Uncopyable
{
};
这样将连接期间的错误转到了编译期间。
Item7 Declare destructors virtual in polumorphic base classes.
如果一个基类为实现多态而设计,则应该为其声明一个virtual析构函数。如果class带有任何virtual函数,也应该拥有一个virtual析构函数。
否则出现的问题如下:如果用一个基类指针指向派生类的对象,则在delete基类指针的时候,会出现派生类成分没有被销毁的后果。
如果一个类的设计不是作为基类使用,不是为了多态性,不应该声明virtual析构函数。
Item8 Prevent exceptions from leaving destructors.
析构函数不要抛出异常。如果被析构函数调用的函数可能抛出异常,则析构函数应该捕捉异常,然后吞下他们。
以下为close抛出异常后,程序异常终止。或者去掉std::abort的调用,析构函数将异常吞下。
class DBConnection
{
public:
static DBConnection create();
void close(); //失败则抛出异常
};
class DBConn
{
public:
~DBConn()
{
try
{
db.close();
}
catch(...)
{
制作运转记录,记下对close的调用失败;
std::abort();
}
}
private:
DBConnection db;
};
如果客户需要对某个操作函数运行期间抛出的异常做出反应,则类应该提供一个普通的函数执行该该做,而非析构函数。
class DBConn
{
public:
void close()
{
db.close();
closed=true;
}
~DBConn()
{
try
{
if(!closed)
{
db.close();
}
}
catch(...)
{
std::abort();
}
}
private:
DBConnection db;
bool closed;
};
由客户在调用DBConn的close的过程中,去捕捉异常并进行一场处理。
Item9 Never class virtual functions during constructions or destructions.
在构造函数和析构函数中,调用virtual函数,类调用从不下降至派生类。因为在构造的过程中,先构造基类的成员,此时基类还未构造,顾不可能调用一个未构造的对象的成员。“早基类构造期间,virtual函数不是virtual函数”。析构也是同样的道理,析构的过程与构造的过程相反,等到调用基类的析构函数的时候,派生类的析构函数已经调用过。
Item10 Have assignment operators return a reference to *this.
class Widget
{
public:
Widget& operator=(const Widget& rhs)
{
....
return *this;
}
};
对于+=、-=、*=等也成立。
Item11 Handle assignment to self in operator=.
处理方式有如下几种:(1)进行“自我赋值”的检查:
class Widget
{
public:
Widget& operator=(const Widget& rhs)
{
if(this==&rhs)
return *this;
...
}
};
(2)复制原有对象的内容
class Bitmap
{
};
class Widget
{
public:
Widget& operator=(const Widget& rhs)
{
Bitmap* pOrig=pb;
pb=new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
private:
Bitmap *pb;
};
(3)copy and swap技术
class Widget
{
public:
void swap(Widget& rhs);
Widget& operator=(const Widget& rhs);
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
Item 12 Copy all parts of an object.
如果要自定义拷贝构造函数和赋值运算符,则要注意拷贝对象内的所有成员变量及基类的成份。
例如:
class Customer
{
public:
Customer(const Customer& rhs)
{
this->name=rhs.name;
}
Customer& operator=(const Customer& rhs)
{
if(this==&rhs)
return *this;
this->name=rhs.name;
return *this;
}
private:
std::string name;
};
class PriorityCustomer:public Customer
{
public:
PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs),
priority(rhs.priority)
{
}
PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
Customer::operator=(rhs);
priority=rhs.priority;
return *this;
}
private:
int priority;
};
另外,不要尝试在某个拷贝函数实现另一个拷贝函数。应该将重复代码部分放进第三个函数,供拷贝构造函数和赋值运算符调用。