C++ Primer 学习笔记_97_用于大型程序的工具 --多重继承与虚继承[续2]

用于大型程序的工具

--多重继承与虚继承[续2]

七、特殊的初始化语义

从具有虚基类的类继承的类对初始化进行特殊处理:在虚基类中,由最低层派生类的构造函数初始化虚基类。在ZooAnimal示例中,使用常规规则将导致Bear 类和 Raccoon类都试图初始化Panda对象的ZooAnimal类部分。

虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式。只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化只在创建中间类型的对象时使用。

Bear::Bear(std::string name, bool onExhibit):
    ZooAnimal(name, onExhibit, "Bear") { }
Raccoon::Raccoon(std::string name, bool onExhibit):
    ZooAnimal(name, onExhibit, "Raccoon") { }

虽然ZooAnimal不是Panda的直接基类,但是Panda构造函数也初始化ZooAnimal基类:Panda::Panda(std::stringname, bool onExhibit):

    ZooAnimal(name, onExhibit, "Panda"),
    Bear(name, onExhibit),
    Raccoon(name, onExhibit),
    Endangered(Endangered::critical),
    sleeping_flag(false) { }

1、怎样构造虚继承的对象

    Bear winnie("pooh");
    Raccoon meeko("meeko");
    Panda yolo("yolo");

当创建Panda对象时:
1. 首先使用构造函数初始化列表中指定的初始化式构造 ZooAnimal 部分。 
2. 接下来,构造 Bear 部分。忽略 Bear 的用于 ZooAnimal 构造函数初始化列表的初始化式。 
3. 然后,构造 Raccoon 部分,再次忽略 ZooAnimal 初始化式。 
4. 最后,构造 Panda 部分。
如果Panda构造函数不显式初始化ZooAnimal基类,就使用ZooAnimal默认构造函数:如果 ZooAnimal 没有默认构造函数,则代码出错。

2、构造函数与析构函数次序
无论虚基类出现在继承层次中的什么地方,总是在构造非虚函数之前构造虚基类!
例如,下面毫无规律的 TeddyBear中,有两个虚基类:ToyAnimal 基类和派生 Bear 的间接基类 ZooAnimal:

class Character
{
    //...
};
class BookCharacter : public Character
{
    //...
};

class ToyAnimal
{
    //...
};

class TeddyBear : public BookCharacter,public Bear,
    virtual public ToyAnimal
{
    //...
};

按声明次序检查直接基类,确定是否存在虚基类。按从根类开始向下到最低层派生类的次序检查每个子树。

TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是BookCharacter,它导致调用Character构造函数,然后是Bear。因此,为了创建TeddyBear对象,按下面次序调用构造函数:

	 ZooAnimal();
	 ToyAnimal();
	 Character();
	 BookCharacter();
	 Bear();
	 TeddyBear();

在这里,由最低层派生类TeddyBear指定用于ZooAnimal和ToyAnimal的初始化式。
在合成复制构造函数中使用同样的构造次序,在合成赋值操作符中也是按这个次序给基类赋值。保证调用基类析构函数的次序与构造函数的调用次序相反。

P627 习题17.35 继承层次如图:

class Class
{
    //...
};
class Base : public Class
{
    //...
};

class Derived1 : virtual public Base
{
    //...
};
class Derived2 : virtual public Base
{
    //...
};

class MI : public Derived1,public Derived2
{
    //...
};

class Final : public MI,public Class
{
    //...
};

//a构造次序
Class();
Base();
Derived1();
Derived2();
MI();
Class();
Final();

//a析构次序
~Finale();
~Class();
~MI();
~Derived2();
~Derived1();
~Base();
~Class();

//b:一个Final对象有一个Base对象,两个Class对象。

//c
    Base *pb;
    Class *pc;
    MI *pmi;
    Derived2 *pd2;

    pb = new Class;   //Error
    pmi = pb;         //Error
    pc = new Final;   //Error
    pd2 = pmi;  //OK

//习题17.36
class Class
{
    //...
};
class Base : public Class
{
public:
    Base();
    Base(std::string);
    Base(const Base &);

protected:
    std::string name;
    //...
};

class Derived1 : virtual public Base
{
public:
    Derived1():Base() {}
    Derived1(std::string &s):Base(s) {}
    Derived1(const Derived1 &d):Base(d) {}
    //...
};
class Derived2 : virtual public Base
{
public:
    Derived2():Base() {}
    Derived2(std::string &s):Base(s) {}
    Derived2(const Derived2 &d):Base(d) {}
    //...
};

class MI : public Derived1,public Derived2
{
public:
    MI():Base() {}
    MI(std::string &s):Base(s),Derived1(s),Derived2(s) {}
    MI(const MI &m):Base(m),Derived1(m),Derived2(m) {}
    //...
};

class Final : public MI,public Class
{
public:
    Final():Base() {}
    Final(std::string &s):Base(s),MI(s) {}
    Final(const Final &f):Base(f),MI(f) {}
    //...
};

C++ Primer 学习笔记_97_用于大型程序的工具 --多重继承与虚继承[续2]

时间: 2024-08-05 07:08:13

C++ Primer 学习笔记_97_用于大型程序的工具 --多重继承与虚继承[续2]的相关文章

C++ Primer 学习笔记_96_用于大型程序的工具 --多重继承与虚继承[续1]

用于大型程序的工具 --多重继承与虚继承[续1] 四.多重继承下的类作用域 成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在本类中查找,然后依次查找每个基类.在多重继承下,查找同时检察所有的基类继承子树 -- 在我们的例子中,并行查找 Endangered子树和Bear/ZooAnimal子树.如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的. [小心地雷] 当一个类有多个基类的时候,通过对所有直接基类同时进行名字查

C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承

用于大型程序的工具 --多重继承与虚继承 引言: 大多数应用程序使用单个基类的公用继承,但是,在某些情况下,单继承是不够用的,因为可能无法为问题域建模,或者会对模型带来不必要的复杂性. 在这些情况下,多重继承可以更直接地为应用程序建模.多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性. 一.多重继承 1.定义多个类 为了支持多重继承,扩充派生列表: class Bear : public ZooAnimal { //... }; 以支持由逗号分隔的基类列表: cla

C++ Primer 学习笔记_89_用于大型程序的工具 --异常处理[续2]

用于大型程序的工具 --异常处理[续2] 八.自动资源释放 考虑下面函数: void f() { vector<string> v; string s; while (cin >> s) { v.push_back(s); } string *p = new string[v.size()]; //... delete p; } 在正常情况下,数组和vector都在退出函数之前被撤销,函数中最后一个语句释放数组,在函数结束时自动撤销vector. 但是,如果在函数内部发生异常,则将

C++ Primer 学习笔记_90_用于大型程序的工具 --异常处理[续3]

用于大型程序的工具 --异常处理[续3] 九.auto_ptr类[接上] 5.auto_ptr对象的复制和赋值是破坏性操作 auto_ptr和内置指针对待复制和赋值有非常关键的区别.当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态. auto_ptr<string> strPtr1(new string("HELLO!")); auto_ptr<

C++ Primer 学习笔记_93_用于大型程序的工具 --命名空间[续2]

用于大型程序的工具 --命名空间[续2] 五.类.命名空间和作用域 名字的可见性穿过任意嵌套作用域,直到引入名字的块的末尾. 对命名空间内部使用的名字的查找遵循常规C++查找规则:当查找名字的时候,通过外围作用域外查找.对命名空间内部使用的名字而言,外围作用域可能是一个或多个嵌套的命名空间,最终以全包围的全局命名空间结束.只考虑已经在使用点之前声明的名字,而该使用仍在开放的块中: namespace A { int i; namespace B { int i; int j; int f1()

C++ Primer 学习笔记_94_用于大型程序的工具 --命名空间[续3]

用于大型程序的工具 --命名空间[续3] 六.重载与命名空间 正如我们所见,每个命名空间维持自己的作用域,因此,作为两个不同命名空间的成员的函数不能互相重载.但是,给定命名空间可以包含一组重载函数成员. 1.候选函数与命名空间 命名空间对函数匹配有两个影响.一个影响是明显的:using声明或using 指示可以将函数加到候选集合.另一个影响则微妙得多. 正如前节所见,有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间.这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类

C++ Primer 学习笔记_91_用于大型程序的工具 --命名空间

用于大型程序的工具 --命名空间 引言: 在一个给定作用域中定义的每个名字在该作用域中必须是唯一的,对庞大.复杂的应用程序而言,这个要求可能难以满足.这样的应用程序的全局作用域中一般有许多名字定义.由独立开发的库构成的复杂程序更有可能遇到名字冲突 -- 同样的名字既可能在我们自己的代码中使用,也可能(更常见地)在独立供应商提供的代码中使用. 库倾向于定义许多全局名字 -- 主要是模板名.类型名或函数名.在使用来自多个供应商的库编写应用程序的时候,这些名字中有一些几乎不可避免地会发生冲突,这种名字

C++ Primer 学习笔记_92_用于大型程序的工具 --命名空间[续1]

用于大型程序的工具 --命名空间[续1] 二.嵌套命名空间 一个嵌套命名空间即是一个嵌套作用域 -- 其作用域嵌套在包含它的命名空间内部.嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽.嵌套命名空间内部定义的名字局部于该命名空间.外围命名空间之外的代码只能通过限定名引用嵌套命名空间中的名字. 嵌套命名空间可以改进库中代码的组织: namespace cplusplus_primer { namespace QueryLib { class Quer

C++ Primer 学习笔记_87_用于大型程序的工具 --异常处理

用于大型程序的工具 --异常处理 引言: C++语言包括的一些特征在问题比較复杂,非个人所能管理时最为实用.如:异常处理.命名空间和多重继承. 相对于小的程序猿团队所能开发的系统需求而言,大规模编程[往往涉及数千万行代码]对程序设计语言的要求更高.大规模应用程序往往具有下列特殊要求: 1.更严格的正常运转时间以及更健壮的错误检測和错误处理.错误处理常常必须跨越独立开发的多个子系统进行[异常处理]. 2.能够用各种库(可能包括独立开发的库)构造程序[命名空间]. 3.能够处理更复杂的应用概念[多重