第11课 继承构造函数和委派构造函数

一、继承构造函数

(一)函数继承同名覆盖

  1. 派生类可以自动获得基类的成员变量和接口(虚函数和纯虚函数,这里指都是public派生),这体现了类的继承性。

  2. 如果基类的非虚函数在派生类被重写,则将发生同名覆盖现象。即基类函数在派生类中会被同名函数隐藏,从而不可见特别是构造函数也不例外,基类的构造函数在派生类中将不可见,如果需要这些构造函数,则需在派生类中显式声明这些接口。

(二)继承构造函数

  1. C++0x中如果派生类要使用基类成员函数,可以通过using声明来完成。C++11中,这种用法被扩展到构造函数,即子类可以通过使用using来声明继承基类的构造函数。但是一旦使用继承构造函数,编译器就不会再为派生类生成默认构造函数了。

  2. 继承构造函数只会初始化基类中的成员变量,对于派生类中的成员变量则需由派生类自行初始化(如“就地初始化”、初始化列表等)。

  3. 如果基类构造函数的参数有默认值,编译器会产生多个构造函数,并被派生类继承(含默认值)

  4. 在多继承体系中,如果某些基类构造函数参数个数和类型都相同,那么在继承构造函数时会发生“冲突,可以在派生类中显式定义那些“冲突”的构造函数,从而阻止隐式生成相应的继承构造函数来解决冲突。

  5. 如果基类的构造函数被声明为private,或者派生类是从基类中虚继承的,就不能在派生类中声明继承构造函数

  6. C++11中继承构造函数与派生类的各种类默认函数一样,是隐式声明的。即如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。这比显式声明函数更加节省目标代码空间。

【编程实验】继承构造函数

#include <iostream>
using namespace std;

//1. 基类
struct Base
{
    Base() { cout << "Base()" << endl; }
    Base(int i) { cout << "Base(int i)" << endl; }
    Base(double d, int i) { cout << "Base(double d, int i)" << endl; }

    Base(float f, int i, const char* c) 
    {
        cout << "Base(float f, int i, const char* c) " << endl;
    }

    void func(double i)  //非虚函数
    {
        cout << "Base::func(double i) : " << i << endl;
    }
};

//1.1 显式声明构造函数
struct NonUsingDerived : public Base
{
    int d{ 0 }; //就会初始化派生类成员

    //注意:
    //1. 基类自定义了构造函数,编译器不再为派生类提供默认构造函数
    //2. 基类的构造函数在派生类中是被隐藏的,无法“透传”给基类。为了使用基类构
    //   造函数,需在派生类中一个一个地显式声明(明显很笨拙),如下
    NonUsingDerived(int i): Base(i){ cout << "NonUsingDerived(int i)" << endl; }
    NonUsingDerived(double d, int i) : Base(d, i) { cout << "NonUsingDerived(double d, int i)" << endl; }

    NonUsingDerived(float f, int i, const char* c) : Base(f, i, c)
    {
        cout << "NonUsingDerived(float f, int i, const char* c) " << endl;
    }

    void func(int i)  //与基类非虚函数同名,这里会将基类同名函数隐藏,从而不可见。
    {
        cout <<"NonUsingDerived::func(int) : " << i << endl;
    }
};

//1.2 使用using继承构造函数
struct UsingDerived : public Base
{
    int d{ 0 }; //就会初始化派生类成员

    //注意:使用using继承基类的构造函数 
    using Base::Base;  //继承构造函数

    using Base::func; //使用基类void func(double)函数
    void func(int i)  //
    {
        cout << "UsingDerived::func(int) : " << i << endl;
    }   
};

//2. 带默认值的构造函数
struct Parent
{
    int a;
    double d;
    Parent(int a = 3, double d = 2.4) : a(a), d(d)
    {
        cout << "Parent(int, double)" << endl;
    }
    void show()
    {
        cout <<"a = " << a << ", d = " << d<< endl;
    }
};

struct Child : public Parent
{
    //由于基类构造函数带默认参数值,使用using继承时,其实分解为
    //Parent(int,double)、Parent(int)、Parent()等并继承下来(含默认值)。
    using Parent::Parent; //继承基类的构造函数
};

int main()
{
    //1. 显式声明构造函数与using的比较
    //NonUsingDerived d1;  //编译失败,派生类没显式声明无参构造函数。
    NonUsingDerived d2(1);
    d2.func(1);    //调用派生类自己的void func(int)
    d2.func(1.5);  //经隐式转换,仍会调用派生类的void func(int),因为基类void func(double)被隐藏。

    UsingDerived d3; //ok, 继承了基类的无参构造函数。
    UsingDerived d4(1.0, 2); 
    d4.func(1);   //调用派生类自己的void func(int)
    d4.func(1.5); //调用基类的void func(double)

    cout <<"----------------------------------------------------------------------------" << endl;
    //2. 带默认值的构造函数的继承
    Child c1;
    c1.show();

    Child c2(4);
    c2.show();

    Child c3(4, 1.0);
    c3.show();

    return 0;
}
/*输出结果
Base(int i)
NonUsingDerived(int i)
NonUsingDerived::func(int) : 1
NonUsingDerived::func(int) : 1
Base()
Base(double d, int i)
UsingDerived::func(int) : 1
Base::func(double i) : 1.5
----------------------------------------------------------------------------
Parent(int, double)
a = 3, d = 2.4
Parent(int, double)
a = 4, d = 2.4
Parent(int, double)
a = 4, d = 1
*/

二、委派构造函数

(一)概述

  1. 在C++11中,所谓委派构造,就是指委派函数将构造任务委托给某个目标构造函数来完成类构造的一种方式。通俗地讲,就是允许构造函数通过初始化列表方式来调用同一个类中的另一个构造函数

  2. 委派构造函数和目标构造函数是调用者和被调用者的关系

  3. 在多构造函数类中,通过委派给其他构造函数,将使编码更简洁。

  4. 目标构造函数是一个构造函数,而不是普通的成员函数。

(二)委派构造函数

  1. 作为委派的目标构造函数,一般会抽象成最为“通用”的基本版本,以方便其他构造函数的调用。

  2. 委派构造函数在初始化列表位置进行构造和委派

  3. 在委派构造函数的初始化列表中只允许有目标构造函数,不能有其他项的初始化。如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。

  4. 目标构造函数总是先于委派构造函数执行(这是由于C++中初始化列表总是先于构造函数被调用)。因此避免目标构造函数和委派构造函数体中初始化同样的成员通常是必要的

  5. 当构造函数较多时,可能拥有不止一个委派构造函数,而一些目标构造函数很可能也是委派构造函数。因此,委派构造函数关系中形成链状的构造关系,但不能形成“委派环”

(三)委派构造函数的应用

  1. 模板构造函数:通过构造函数模板来产生目标构造函数,再利用委派使构造函数具有泛型编程的能力

  2. 异常处理方面,如果在委派构造函数中使用try的话,那么从目标构造函数产生的异常,都可以在委派构造函数中被捕捉到

    (1)这是一种在构造函数体外catch异常的方式,C++会自动重新将异常抛出给对象创建者,哪怕我们没有显式throw该异常

    (2)这种构造函数体外的try-catch语句,可以同时捕获基类或数据成员抛出的异常。此外还有一个用途,就是用来转换捕捉到的异常对象。

【编程实验】委派构造函数

#include <iostream>
#include <vector>
#include <deque>
#include <list>
using namespace std;

//1. 委派构造函数
//1.1 不使用委派构造的类
class Info1
{
private:
    int type;
    char name;
    void initRest()
    {
        type += 1; //其它初始化任务
    }
public:
    //三个构造函数初除了初始化列表不同,函数体都是一样的。如果initRest代码量大,存在
    //严重的代码重复现象。
    Info1() : type(1), name('a') { initRest(); }
    Info1(int i) : type(i), name('a') { initRest(); } //type(i)先于initRest执行!
    Info1(char c) : type(1), name{ 'e' }{initRest(); }

    void print()
    {
        cout << "type = " << type << ", name = " << name << endl;
    }
};

//1.2 使用委派构造函数
class Info2
{
private:
    int type;
    char name;

    //目标构造函数(这里一般设为private)
    Info2(int i, char e) : type(i),name(e)
    {
        type += 1;  //其它初始化
    }
public:
    //三个构造函数为委派构造函数,会将构造任务委托相应的目标构造函数!
    Info2() : Info2(1, 'a') {}    //委派给Info2(int i, char e)目标函数去构造
    Info2(char c) : Info2(1, c){} //委派给Info2(int i, char e) 去构造

    //Info2(int i) : Info2(),type(i) {} //编译失败!委派构造初始化列表不能有目标构造函数以外的成员!
    Info2(int i) : Info2() { type = i;} //委派给Info2() 去目标构造函数

    void print()
    {
        cout << "type = " << type << ", name = " << name << endl;
    }
};

//2. 构造函数模板+委派,使构造函数具有泛型编程能力
class TDConstructed  //Template delegate construct
{
private:
    std::list<int> ls;

    //构造函数模板:构造一个容器,用于采集从first到last之间的元素(T为迭代器类型)
    template<typename T> TDConstructed(T first, T last) : ls(first, last)
    {
    }
public:
    //利用委派,使该类能接受多种容器对其进行初始化。
    TDConstructed(vector<short>& v) : TDConstructed(v.begin(), v.end()) {}
    TDConstructed(deque<int>& d) : TDConstructed(d.begin(), d.end()) {}

    void print()
    {
        for (const auto& elem : ls)
        {
            cout << elem << " ";
        }
        cout << endl;
    }
};

class Widget
{
public:
    Widget() { cout << "Widget() : " << this << endl; }
    ~Widget() {cout << "~Widget() : " << this << endl;}
};

//3. 利用委派构造来捕获构造函数出现的异常
class ExceptDemo  //Delegate Constructor Exception
{
private:
    int type;
    double data;
    Widget w;

    //目标构造函数
    ExceptDemo(int i, double d) : type(i), data(d)
    {
        cout << "going to throw!" << endl;
        throw 0;  
    }
public:
    //构造函数块外的catch语句(上面这种)即使没有显示地重新抛出异常,c++也会自动抛出
    //委派给ExceptDemo(int, double)去构造。
    ExceptDemo(double d) try : ExceptDemo(1, d)   //注意,try块是整个构造函数体,而异常是构造函数体外被catch!
    {                                             //这种方式可以同时捕获基类或数据成员抛出的异常!
        //目标构造函数抛出异常,这里没机会执行!这样设计是合理的,当构造函数出现异常
        //不应该再继续执行这段代码去构造了。
        cout << "Run the body." << endl; 
        //其他初始化
    }
    catch (...)
    {
        cout << "ExceptDemo(): caught exception." << endl; //捕获到异常!!!执行到这里。(注意,此时成员对象Widget会被释放)

        //.....             //这里可以做一些清理工作,如delete一些堆对象操作,关闭一些句柄等操作
        //throw;            //标准的做法是:在这里继续传递异常给对象的创建者。但这种在构造函数块外的catch语句,即使没有
                            //显式地重新抛出异常,c++也会自动将异常重新抛出。
    }

    ~ExceptDemo()
    {        
        cout <<"~ExceptDemo()" << endl;
    }
};

int main()
{
    //1. 委派构造函数和目标构造函数
    Info1 obj1(3);
    obj1.print();  //type = 4, name = a

    Info2 obj2(3);
    obj2.print();  //type = 3, name = a(由于目标构造函数先执行,即type=4,但
                   //随后执行委派构造函数,又执行type =i,即type又变成3。

    vector<short> v = { 1,2,3,4,5 };
    deque<int> d = { 6,7,8,9 };

    //2. 构造函数模板化
    TDConstructed td1(v);
    td1.print();

    TDConstructed td2(d);
    td2.print();

    //3. 在委派构造函数中捕获构目标构造函数异常
    try {
        ExceptDemo ed(1.2);
    }
    catch (...)
    {
        cout <<"main():caught exception." << endl;
    }

    return 0;
}
/*输出结果
type = 4, name = a
type = 3, name = a
1 2 3 4 5
6 7 8 9
Widget() : 003BF880
going to throw!
~Widget() : 003BF880
ExceptDemo(): caught exception.
main():caught exception.
*/

原文链接: https://www.cnblogs.com/5iedu/p/11291124.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第11课 继承构造函数和委派构造函数

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/392744

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午2:57
下一篇 2023年4月3日 下午2:57

相关推荐