C++继承与构造函数、复制控制

        每个派生类对象由派生类中定义的(非static)成员加上一个或多个基类子对象构成,因此,当构造、复制、赋值和撤销派生类型对象时,也会构造、复制、赋值和撤销这些基类子对象。

        构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。

 

1:构造函数和继承

       派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。

 

        派生类的合成默认构造函数:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
    }
};
int main()
{
    son ss1;
    ss1.display();
}

        执行”son ss1;”语句时,调用派生类son的合成的默认构造函数,该函数会首先调用基类的默认构造函数。上述代码的结果是:

father constructor
[son]publ_i is 1
[son]prot_i is 3


        如果派生类自己定义了构造函数,则该构造函数会隐式调用基类的默认构造函数:

class son: public father
{
public:
    son(int a = 2):mypriv_i(a)
    {
        cout << "son constructor" << endl;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};

int main()
{
    son ss1;
    ss1.display();
}

        执行”son ss1;”语句时,调用派生类son的默认构造函数,该函数首先调用基类的默认构造函数初始化基类部分,然后,使用初始化列表初始化son::mypriv_i,最后,在执行son构造函数的函数体。上述代码的结果是:

father constructor
son constructor
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 2


        派生类构造函数还可以明确的在初始化列表中调用基类的构造函数,来间接的初始化基类成员。注意,在派生类列表中,不能直接初始化基类成员:

class son: public father
{
public:
    son(int a = 2):mypriv_i(a),father(100)
    {
        cout << "son constructor" << endl;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};
int main()
{
    son ss1;
    ss1.display();
}

        不管初始化列表中的顺序如何,派生类构造函数,都是首先调用基类构造函数初始化基类成员,然后是根据派生类成员的声明顺序进行初始化。

         如果尝试在初始化列表中直接初始化基类成员,会发生编译错误:

    son(int a = 2):mypriv_i(a),publ_i(100)
    {
        cout << "son constructor" << endl;
    }

        报错如下:

test2.cpp: In constructor ‘son::son(int)’:
test2.cpp:29:29: error: class ‘son’ does not have any field named ‘publ_i’
  son(int a = 2):mypriv_i(a),publ_i(100)
                             ^

        派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 public 或 protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。

        注意,一个类只能初始化自己的直接基类。

 

2:复制控制和继承

        派生类合成的复制控制函数,对对象的基类部分连同派生部分的成员一起进行复制、赋值或撤销,自动调用基类的复制构造函数、赋值操作符或析构函数对基类部分进行复制、赋值或撤销:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
    }
    father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
    {
        cout << "father copy constructor" << endl;
    }
    
    father& operator=(const father& src)
    {
        publ_i = src.publ_i;
        priv_i = src.priv_i;
        prot_i = src.prot_i;
        
        cout << "father operator = " << endl;
        return *this;
    }
    ~father()
    {
        cout << "father destructor" << endl;
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a = 2):mypriv_i(a)
    {
        cout << "son constructor" << endl;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};
int main()
{
    son ss1(3);
    son ss2(ss1);
    ss2.display();
    
    son ss3;
    ss3 = ss1;
    ss3.display();
}

        “son ss2(ss1);”语句,将调用派生类son的合成的复制构造函数,该构造函数将会自动调用基类的复制构造函数;”ss3 = ss1;”语句,将调用派生类son的合成的赋值操作符函数,该函数会自动调用基类的赋值操作符函数;最后,程序退出之前,会将派生类son的三个对象进行析构,从而自动调用基类的析构函数;上述代码的结果如下:

father constructor
son constructor
father copy constructor
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 3
father constructor
son constructor
father operator = 
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 3
father destructor
father destructor
father destructor


        如果派生类自己定义了复制构造函数或赋值操作符,则应该显示的调用基类的复制构造函数或赋值操作符:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
    }
    father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
    {
        cout << "father copy constructor" << endl;
    }
    
    father& operator=(const father& src)
    {
        publ_i = src.publ_i;
        priv_i = src.priv_i;
        prot_i = src.prot_i;
        
        cout << "father operator = " << endl;
        return *this;
    }
    ~father()
    {
        cout << "father destructor" << endl;
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a = 2):mypriv_i(a),father(100)
    {
        cout << "son constructor" << endl;
    }
    son(const son& src):mypriv_i(src.mypriv_i),father(src)
    {
        cout << "son copy constructor" << endl;
    }
    son& operator=(const son& src)
    {
        father::operator=(src);
        mypriv_i = src.mypriv_i;
        
        cout << "son operator = " << endl;
        return *this;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};
int main()
{
    son ss1(3);
    son ss2(ss1);
    ss2.display();
    
    son ss3;
    ss3 = ss1;
    ss3.display();
}

        在派生类son的复制构造函数中,初始化列表中显示调用基类复制构造函数使用src初始化基类部分。这里如果省略调用基类复制构造函数的调用的话,编译器会自动调用基类的默认构造函数初始化基类部分,这就造成新构造的对象会有比较奇怪的配置,它的基类部分保存默认值,而他的派生类部分是src的副本;

        在派生类son的赋值操作符函数中,显示调用了基类的赋值操作符函数。如果省略了这一调用,则赋值操作,仅仅使该对象的mypriv_i与src的mypriv_i相同;

        上述代码的结果如下:

father constructor
son constructor
father copy constructor
son copy constructor
[son]publ_i is 100
[son]prot_i is 3
[son]mypriv_i is 3
father constructor
son constructor
father operator = 
son operator = 
[son]publ_i is 100
[son]prot_i is 3
[son]mypriv_i is 3
father destructor
father destructor
father destructor


        如果定义了派生类的析构函数,则不同于复制构造函数和赋值操作符:编译器会自动调用基类的析构函数,因此在派生类的析构函数中,只负责清除自己的成员:

    ~son()
    {
        cout << "son destructor" << endl;
    }

        定义了son的析构函数之后,上述代码的结果是:

...
son destructor
father destructor
son destructor
father destructor
son destructor
father destructor

        对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。

 

        另外,如果基类指针指向了一个派生类对象,则删除该基类指针时,如果基类的析构函数不是虚函数的话,则只会调用基类的析构函数,而不会调用派生类析构函数。比如还是上面的father和son两个类,运行下面的代码:

    father *fp = new son;
    delete fp;

        结果就是:

father constructor
son constructor
father destructor

        为了避免这种情况的发生,应该将基类的析构函数定义为虚函数,这样,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。从而可以得到正确的结果。

        因此,即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

 

3:构造函数或析构函数中调用虚函数

        在运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。

        因此,如果是在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。如果在基类构造函数中调用虚函数,则构造派生类的时候,该虚函数实际上还是调用的基类的版本:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
        display();
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }

private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son()
    {
        cout << "son constructor" << endl;
    }
    
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
    }
};

int main()
{
    father *fp = new son;
}

        在构造son对象时,需要调用father的构造函数,在father的构造函数中调用了虚函数display。此时,虽然构造的是派生类对象,但是这里依然还是调用基类的display函数。因此,上述代码的结果为:

father constructor
[father]publ_i is 1
[father]priv_i is 2
[father]prot_i is 3
son constructor


        无论由构造函数(或析构函数)直接调用虚函数,或者从构造函数(或析构函数)所调用的函数间接调用虚函数,都应用这种绑定。

        要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员,但是,对象的派生部分的成员不会在基类构造函数运行期间初始化,实际上,如果允许这样的访问,程序很可能会崩溃。

原文地址:https://www.cnblogs.com/gqtcgq/p/7247022.html