派生类的赋值运算符/赋值构造函数也必须处理它的基类成员的赋值

 

一般情况下如果自己不定义赋值构造函数及赋值操作符函数,C++会给生成默认的,对于基本数据类型一般没啥问题,但是对于指针或是对象了就有些问题,需要自己写。

 

《C++ primer》关于自己定义派生类复制构造函数和赋值操作符有介绍。自己总结如下:

 

如果没有必要自己写复制构造函数和赋值操作符,那就可以用系统默认的,能够很好的完成要求;但是一旦决定要自己显式定义,则该定义将完全覆盖默认定义。

 

对派生类进行复制构造时,如果想让基类的成员也同时拷贝,就一定要在派生类复制构造函数初始化列表中显示调用基类复制构造函数(当然在函数体内将基类部分的值拷贝也是可以的,只不过它是先用默认构造函数初始化后再修改的基类成员变量的值,效率比较低),否则它会调用基类的默认构造函数,而不会对基类的成员变量拷贝值,这样生成的对象,它的派生类部分和被复制的对象派生类部分一样,而基类部分则是默认构造函数的初始化结果。

 

派生类赋值操作符函数与复制构造函数类似,如果派生类要定义自己的复制操作符,则该操作符必须对基类部分进行显式赋值。

 

见C++primer P495页

 

派生类的赋值运算符也必须处理它的基类成员的赋值!看看下面:

class base {
public:
  base(int initialvalue = 0): x(initialvalue) {}

private:
  int x;
};

class derived: public base {
public:
  derived(int initialvalue)
  : base(initialvalue), y(initialvalue) {}

  derived& operator=(const derived& rhs);

private:
  int y;
};

逻辑上说,derived的赋值运算符应该象这样:

// erroneous assignment operator
derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;    // 见条款17

  y = rhs.y;                         // 给derived仅有的
                                     // 数据成员赋值

  return *this;                      // 见条款15
}

不幸的是,它是错误的,因为derived对象的base部分的数据成员x在赋值运算符中未受影响。例如,考虑下面的代码段:

void assignmenttester()
{
  derived d1(0);                      // d1.x = 0, d1.y = 0
  derived d2(1);                      // d2.x = 1, d2.y = 1

  d1 = d2;         // d1.x = 0, d1.y = 1!
}

请注意d1的base部分没有被赋值操作改变。

// 正确的赋值运算符
derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;

  base::operator=(rhs);    // 调用this->base::operator=
  y = rhs.y;

  return *this;
}

 

=============================================================================

有一篇好帖子在此:http://blog.csdn.net/huqinwei987/article/details/7533401

复制构造函数和赋值操作符

 

[cpp] view plaincopy

#include<iostream>  
class Base {  
public:  
    Base(double t = 1.2) : test(t) {}  
public:  
    double test;  
      
};  
class Derived : public Base {  
public://复制构造函数不会自动唤醒  
    //Base::Base(const Base&) not invoked automatically  
    Derived(double t = 2.2) : Base(t){}  
    Derived(const Derived& d) :  
        Base(d) /*other member initialization */ {/*...*/}//显式使用基类复制构造函数Base(d);  
    //Base::~Base invoked automatically编译器显式(编译器调用怎么看显式)自动调用基类析构函数  
    //要注意的是,对象的撤销顺序与构造顺序相反(理所当然),按层次逐个调用析构函数  
    ~Derived() {/*do what it takes to clean up derived members*/}  
public:  
    Derived&  
    operator=(const Derived&);  
      
};  
//Base::operator=(const Base&) not invoked automatically  
Derived& Derived::operator=(const Derived &rhs){  
    if(this != &rhs) {//必须防止自身赋值  
        Base::operator=(rhs);   //assign the base part(给基类部分赋值)  
        //do whatever needed to clean up the old value in the derived part(给其他部分清楚或者赋值)  
        //assign the members from the derived  
    }  
    return *this;  
      
}  
  
//如果不显式给基类初始化,基类将默认初始化一个  
class Derived2 : public Base{  
public:  
    Derived2(double t) : Base(t){}  
    Derived2(const Derived2& d2) {/*...*/}  
};  
  
int main(){  
    Base b1;  
    std::cout << b1.test << std::endl;  
      
    Derived d1(3.14159);  
    Derived d2(d1);  
    std::cout << d1.test << std::endl;  
    std::cout << d2.test << std::endl;  
      
    Derived2 d3(3.14159);  
    Derived2 d4(d3);                            //复制构造函数如果省略基类的初始化,将调用默认构造函数  
    std::cout << d3.test << std::endl;  
    std::cout << "After copy:" << std::endl;  
    std::cout << d4.test << std::endl;    
}  

  

 

至于析构函数,下边就要说说为什么要virtual析构函数了

delete指向动态分配对象的指针时,需要在从内存清掉之前运行析构函数清除对象。

处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。

 

综合来说:也就是说被基类指针误读为基类对象的派生类对象,析构函数只删除派生类的基类部分,其他地方都没搞定,为了解决这个问题,基类中的destructor必须是virtual。

[cpp] view plaincopy

    virtual ~Base() { std::cout << " ~Base() called" << std::endl; }  
    ~Derived() { std::cout << " ~Derived() called" << std::endl; }  
 [cpp] view plaincopy

Base *itemP = new Base;  
delete itemP;  
std::cout << "=================================================" << std::endl;  
itemP = new Derived;  
delete itemP;  

  

 

利用virtual的“覆盖机制”,让Derived对象不存在Base::~Base(),这样必须先从 ~Derived()开始。。。。。。。

REVIEW:C++的函数调用默认不使用动态绑定,要触发动态绑定,必须满足两个条件:

第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数(但是假如爸爸定义了虚函数,他子孙默认也是虚的),非虚函数不进行动态绑定;

第二,必须通过基类类型(为什么是基类,安全啊,兼容啊,都可以传)的引用或指针进行函数调用,要理解这一要求,需要理解在使用继承层次中某一类型的对象的引用或指针时会发生什么(这不又发生一个么)。

 

前边介绍的时候基本上是给函数传参时,传递一个基类的引用或指针,根据具体对象可以动态的选择执行基类还是派生类的成员函数。)

[cpp] view plaincopy

void print_total(ostream &os, const Item_base &item, size_t n){  
os << *.....*/ << item.net_price() << std::endl;//net_price有两个版本,要调用哪个就看传入的确切对象是什么了  
}  

  


还有,virtual析构函数是不需要同名的(其他的需要同名吧)

posted @ 2013-09-08 14:24  jiayouwyhit  阅读(2482)  评论(0编辑  收藏  举报