编译器为类构建那些默认函数?

条款45:   弄清C++在幕后为你所写、所调用的函数    
   
一个空类什么时候不是空类?   ----   C++编译器通过它的时候。如果你没有声明下列函数,体贴的编译器会声明它自己的版本。这些函数是:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符,一个缺省构造函数。所有这些函数都是公有的。换句话说,如果你这么写:
 
  class   Empty{};   
 
和你这么写是一样的:
     
  
 
  1. class   Empty   {   
  2.   public:   
  3.       Empty();    //   缺省构造函数   
  4.       Empty(const   Empty&   rhs);     //   拷贝构造函数      
  5.       ~Empty();        //   析构函数   ----   是否 为虚函数看下文说明 
  6.       Empty&  operator=(const   Empty&   rhs);         //   赋值运算符       
  7.       Empty*   operator&();                           //   取址运算符   
  8.       const   Empty*   operator&()   const;   
  9.   };   

   
  现在,如果需要,这些函数就会被生成,但你会很容易就需要它们。下面的代码将使得每个函数被生成:
 
   
  const   Empty   e1;                       //  
缺省构造函数
   
  Empty   e2(e1);                           //  
拷贝构造函数
 
  e2   =   e1;                                 //   
赋值运算符

  Empty   *pe2   =   &e2;               //  
取址运算符    (const)      
  const   Empty   *pe1   =   &e1;    //    
取址运算符
   (const)  
   
 
假设编译器为你写了函数,这些函数又做些什么呢?是这样的,缺省构造函数和析构函数实际上什么也不做,它们只是让你能够创建和销毁类的对象(对编译器来说,将一些   "幕后"   行为的代码放在此处也很方便   ----   参见条款33M24。)。注意,生成的析构函数一般是非虚拟的(参见条款14),除非它所在的类是从一个声明了虚析构函数的基类继承而来。缺省取址运算符只是返回对象的地址。这些函数实际上就如同下面所定义的那样:
 
  inline   Empty::Empty()   {}  
  inline   Empty::~Empty()   {}      
  inline   Empty   *   Empty::operator&()   {   return   this;   }      
  inline   const   Empty   *   Empty::operator&()   const   {   return   this;   }  
   
 
至于拷贝构造函数和赋值运算符,官方的规则是:缺省拷贝构造函数(赋值运算符)对类的非静态数据成员进行   "以成员为单位的"   逐一拷贝构造(赋值)。即,如果m是类C中类型为T的非静态数据成员,并且C没有声明拷贝构造函数(赋值运算符),m将会通过类型T的拷贝构造函数(赋值运算符)被拷贝构造(赋值)----   如果T有拷贝构造函数(赋值运算符)的话。如果没有,规则递归应用到m的数据成员,直至找到一个拷贝构造函数(赋值运算符)或固定类型(例如,intdouble,指针,等)为止。默认情况下,固定类型的对象拷贝构造(赋值)时是从源对象到目标对象的   "逐位每一个bit)"   拷贝。对于从别的类继承而来的类来说,这条规则适用于继承层次结构中的每一层,所以,用户自定义的构造函数和赋值运算符无论在哪一层被声明,都会被调用。
 
   
 
我希望这已经说得很清楚了。
 
   
 
但怕万一没说清楚,还是给个例子。看这样一个NamedObject模板的定义,它的实例是可以将名字和对象联系起来的类:
 
   
  template<class   T>  
  class   NamedObject   {  
  public:  
      NamedObject(const   char   *name,   const   T&   value);  
      NamedObject(const   string&   name,   const   T&   value);  
   
      ...  
   
  private:  
      string   nameValue;  
      T   objectValue;  
  };  
   
 
因为NamedObject类声明了至少一个构造函数,编译器将不会生成缺省构造函数;但因为没有声明拷贝构造函数和赋值运算符,编译器将生成这些函数(如果需要的话)。
 
   
 
看下面对拷贝构造函数的调用: 

  NamedObject<int>   no1("Smallest   Prime   Number",   2);   

  NamedObject<int>   no2(no1);      //   调用拷贝构造函数  
   
 
编译器生成的拷贝构造函数必须分别用no1.nameValueno1.objectValue来初始化no2.nameValueno2.objectValuenameValue的类型是stringstring有一个拷贝构造函数(你可以在标准库中查看string来证实   ----   参见条款49),所以no2.nameValue初始化时将调用string的拷贝构造函数,参数为no1.nameValue。另一方面,NamedObject<int>::objectValue的类型是int(因为这个模板实例中,Tint),int没有定义拷贝构造函数,所以no2.objectValue是通过从no1.objectValue拷贝每一个比特(bit)而被初始化的。
 
   
 
编译器为NamedObject<int>生成的赋值运算符也以同样的方式工作,但通常,编译器生成的赋值运算符要想如上面所描述的那样工作,与此相关的所有代码必须合法且行为上要合理。如果这两个条件中有一个不成立,编译器将拒绝为你的类生成operator=,你就会在编译时收到一些诊断信息。
 
   
 
例如,假设NamedObject象这样定义,nameValue是一个string的引用,objectValue是一个const   T
 
   
  template<class   T>  
  class   NamedObject   {  
  public:  
      //  
这个构造函数不再有一个const名字参数,因为
nameValue  
      //  
现在是一个非const   string的引用。char*构造函数
 
      //  
也不见了,因为引用要指向的是
string  
      NamedObject(string&   name,   const   T&   value);  
   
      ...                                                     //  
同上,假设没有
 
                                                                //  
声明
operator=  
  private:  
      string&   nameValue;                       //  
现在是一个引用
 
      const   T   objectValue;                   //  
现在为
const  
  };  
   
 
现在看看下面将会发生什么:
 
   
  string   newDog("Persephone");  
  string   oldDog("Satch");  
   
  NamedObject<int>   p(newDog,   2);    //
正在我写本书时,我们的爱犬Persephone即将过她的第二个生日  

  NamedObject<int>   s(oldDog,   29);    //   家犬Satch如果还活着,会有29岁了(从我童年时算起)  

  p   =   s;                                      //   p中的数据成员将会发生些什么呢?  
   
 
赋值之前,p.nameValue指向某个string对象,s.nameValue也指向一个string,但并非同一个。赋值会给p.nameValue带来怎样的影响呢?赋值之后,p.nameValue应该指向   "s.nameValue所指向的string"   吗,即,引用本身应该被修改吗?如果是这样,那太阳从西边出来了,因为C++没有办法让一个引用指向另一个不同的对象(参见条款M1)。或者,p.nameValue所指的string对象应该被修改吗?
 
   
 
面对这样的难题C++拒绝编译这段代码。如果想让一个包含引用成员的类支持赋值,你就得自己定义赋值运算符。对于包含const成员的类(例如上面被修改的类中的objectValue)来说,编译器的处理也相似;因为修改const成员是不合法的,所以编译器在隐式生成赋值函数时也会不知道怎么办。还有,如果派生类的基类将标准赋值运算符声明为private编译器也将拒绝为这个派生类生成赋值运算符。因为,编译器为派生类生成的赋值运算符也应该处理基类部分(见条款16M33),但这样做的话,就得调用对派生类来说无权访问的基类成员函数,这当然是不可能的。
  (所以类中含有引用类型、 const类型成员 最好重写赋值运算符)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值