EffectiveC++第三章构造函数析构函数和赋值操作符学习笔记


条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符

这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。

 用delete去删除一个已经被删除的指针,其结果是不可预测的。

解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。

对于有些类,当实现拷贝构造函数和赋值操作符非常麻烦的时候,特别是可以确信程序中不会做拷贝和赋值操作的时候,去实现它们就会相对来说有点得不偿失。前面提到的那个遗漏了拷贝构造函数和赋值操作符的例子固然是一个糟糕的设计,那当现实中去实现它们又不切实际的情况下,该怎么办呢?很简单,照本条款的建议去做:可以只声明这些函数(声明为private成员)而不去定义(实现)它们。

// 去掉默认的赋值函数和默认的拷贝构造函数

// vc 2005 express


复制代码
#include  < iostream >

#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  < iostream >

#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  < iostream >

#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  < iostream >

#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指出,一个正确的派生类的赋值运算符必须调用它的每个基类的的赋值运算符

 

复制代码
#include  < iostream >

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;

 ...

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值