条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。
用delete去删除一个已经被删除的指针,其结果是不可预测的。
解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。
对于有些类,当实现拷贝构造函数和赋值操作符非常麻烦的时候,特别是可以确信程序中不会做拷贝和赋值操作的时候,去实现它们就会相对来说有点得不偿失。前面提到的那个遗漏了拷贝构造函数和赋值操作符的例子固然是一个糟糕的设计,那当现实中去实现它们又不切实际的情况下,该怎么办呢?很简单,照本条款的建议去做:可以只声明这些函数(声明为private成员)而不去定义(实现)它们。
// 去掉默认的赋值函数和默认的拷贝构造函数
// vc 2005 express
#include < string >
using std::cout;
using std::endl;
class Fruit
{
public :
Fruit( const char * name);
char * get_name();
private :
Fruit & operator = (Fruit & f); // 去掉默认赋值函数
Fruit(Fruit & f); // 常用于复制参数
protected :
char * _name;
};
Fruit::Fruit( const char * name)
{
if ( name != 0 )
{
_name = new char [strlen(name) + 1 ];
strcpy(_name, name);
}
else
{
_name = new char [ 1 ];
* _name = ' \0 ' ;
}
}
char * Fruit::get_name()
{
return _name;
}
int main()
{
Fruit orange( " orange " );
Fruit apple( " apple " );
// apple = orange; // “Fruit::operator =”: 无法访问private 成员(在“Fruit” 类中声明)
// orange = "banana"; // 二进制“=”: 没有找到接受 “const char [7]”类型的右操作数的运算符
// Fruit asiaApple = apple; // “Fruit::Fruit”: 无法访问private 成员(在“Fruit”类中声明)
cout << orange.get_name() << endl;
system( " pause " );
return 0 ;
}
条款12: 尽量使用初始化而不要在构造函数里赋值.
从纯实际应用的角度来看,有些情况下必须用初始化。特别是const和引用数据成员只能用初始化,不能被赋值。
#include < string >
using std::cout;
using std::endl;
class Tree
{
public :
Tree( const int age) : _age(age),rf(age)
{
// _age = age; // error
}
protected :
const int _age;
const int & rf; // 很糟糕的用法,但说明了赋值不能做的事情
};
int main()
{
system( " pause " );
return 0 ;
}
用成员初始化列表还是比在构造函数里赋值要好。这次的原因在于效率。当使用成员初始化列表时,只有一个string成员函数被调用。而在构造函数里赋值时,将有两个被调用。:一次是缺省构造函数,另一次是赋值。
条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同
类成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初始化列表中列出的顺序没一点关系。我们知道,对一个对象的所有成员来说,它们的析构函数被调用的顺序总是和它们在构造函数里被创建的顺序相反。那么,如果允许上面的情况(即,成员按它们在初始化列表上出现的顺序被初始化)发生,编译器就要为每一个对象跟踪其成员初始化的顺序,以保证它们的析构函数以正确的顺序被调用。这会带来昂贵的开销。所以,为了避免这一开销,同一种类型的所有对象在创建(构造)和摧毁(析构)过程中对成员的处理顺序都是相同的,而不管成员在初始化列表中的顺序如何。
条款14: 确定基类有虚析构函数
当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。
#include < string >
using std::cout;
using std::endl;
class AAA
{
public :
AAA( const char c = ' a ' ):n(c)
{ cout << " new AAA\n " ; }
virtual ~ AAA()
{ cout << " delete AAA\n " ; }
protected :
char n;
};
class BBB : public AAA
{
public :
BBB( const char c ) :n(c)
{ cout << " new BBB\n " ; }
virtual ~ BBB()
{ cout << " delete BBB\n " ;}
protected :
char n;
};
int main()
{
BBB * bbb = new BBB( ' b ' );
delete bbb;
AAA * aaa = new BBB( ' r ' );
delete aaa; // 若没有虚拟析构函数,则会调用基类的中析构函数delete AAA
// 加上virtual 后
// delete BBB
// delete AAA
system( " pause " );
return 0 ;
}
如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。
#include < string >
using std::cout;
using std::endl;
class AAA
{
public :
AAA( const char c = ' a ' ):n(c)
{ cout << " new AAA\n " ; }
virtual char print() = 0 ;
char printa()
{ return n; }
virtual ~ AAA() = 0 ;
protected :
char n;
};
AAA:: ~ AAA()
{ cout << " delete AAA\n " ; }
class BBB : public AAA
{
public :
BBB( const char c ) :n(c)
{ cout << " new BBB\n " ; }
char print()
{ return n; }
virtual ~ BBB()
{ cout << " delete BBB\n " ;}
protected :
char n;
};
int main()
{
// 基类是抽象类,子类必须实现基类中的纯虚函数,
// 基类,可以也无须实现自己的纯虚函数,但析造函数是纯虚函数时,则必须在基类中实现其方法
BBB * bbb = new BBB( ' b ' );
AAA * aaa = new BBB( ' r ' );
cout << " BBB class " << bbb -> print() << endl; // b
cout << " BBB class " << aaa -> printa() << endl; // a
delete bbb;
delete aaa;
// AAA a; // 不能实例化抽象类
system( " pause " );
return 0 ;
}
条款16指出,一个正确的派生类的赋值运算符必须调用它的每个基类的的赋值运算符
using std::cout;
using std::endl;
using std::cin;
class Property
{
public :
Property(unsigned int age, unsigned int weight)
: _age(age), _weight(weight)
{
}
unsigned int _age;
unsigned int _weight;
};
class base
{
public :
base ( int initialvalue = 0 )
: x(initialvalue)
{
prop = new Property( 22 , 99 );
}
void GetWeight()
{
cout << prop -> _weight << endl;
}
void Relase()
{
delete prop;
prop = 0 ;
}
base & operator = ( const base & b)
{
prop = new Property(b.prop -> _age, b.prop -> _weight);
return * this ;
}
private :
int x;
Property * prop;
};
class derived: public base {
public :
derived( int initialvalue)
: base (initialvalue), y(initialvalue) {}
derived & operator = ( const derived & rhs);
private :
int y;
};
derived & derived:: operator = ( const derived & rhs)
{
if ( this == & rhs) return * this ;
base :: operator = (rhs); // 调用 this->base::operator=
y = rhs.y;
return * this ;
}
void assignmenttester()
{
derived d1( 0 );
derived d2( 1 );
d1 = d2;
d2.GetWeight();
d1.GetWeight();
d2.Relase();
d1.GetWeight();
cin. get ();
}
void main()
{
assignmenttester();
}
条款17: 在operator=中检查给自己赋值的情况
采用地址相等的定义,一个普通的赋值运算符看起来象这样:
c& c::operator=(const c& rhs)
{
// 检查对自己赋值的情况
if (this == &rhs) return *this;
...
}