C++ Knowledge series Inheritance & RTTI & Exception Handling

Inheritance

  1. The pointer or reference to base class can address/be assigned with any of the classes derived from base class with virtual functions through mechanism of dynamic binding.
  2. There are three ways to support polymorphism:
    1.  through implicit conversion (from a pointer or reference to derived class to a pointer to base class. Like BaseClass *p = new DerivedClass).
    2.  through mechanism of virtual function.
    3. through keyword dynamic_cast<…>
  3. The base class for derived class must be defined before, forward declaration is insufficient to be used as base class.
  4. The designer must be sure which functions is type relevant. They are virtual functions in class hiberarchy.
  5. virtual void VirtualFunction() = 0; pure virtual function is just a placement in base class, it must be implemented in derived class. However there could be definition of virtual function in order to be called in derived class using base::VirtualFunction.
  6. The name in derived class will hide the same name in base class, including the variable and function with same name regardless of type or return type or type of argument. But you can use keyword using to explore those overridden functions in base class. Like: using BaseClass::Dragon; (not for variable).
  7. The candidate functions of name overload must be in the same namespace.
  8. When using pointer or reference to base class, only virtual function is binding dynamically, all of rest is static binding according to declaration type (static type).
  9. The static variable or function in base class are shared by all objects of base class or derived class.
  10. There is a approach to access the private member in base class, which is to declared derived class as friend member in base class.
  11. Trick:define base constructor as private and has derived class as friend,so the base class only can be inherited by his friend derived class.
  12. In constructor:
    • 1. allocation enough memory;
    • 2. place vptr if needed,which pointer to vtbl of class not object with address slot of virtual function(converted into global function with the first parameter 'this')
    • 3. invoke constructor of base class;
    • 4. initialize the member variables in the order of declaration(copy constructor when using initialization list) ;
    • 5. call the constructor of member variable if needed;
    • 6. execute the body of constructor( call data member’s default constructor with default value, call assignment operator);
    • 7. only call virtual function of this class if needed through static binding.
  13. Trick:You can define constructor or destructor as protected or private to prevent creating a object of class for some special need.
  14. Trick:Private new operation can prevent/prohibit/forbid creating a object on heap using new, but on stack.
  15. Trick:Private destructor can deter creating a object on stack, but allow creating a object on heap by defining a static member function with a pointer parameter, in which the destructor can be invoked to free memory.
  16. In general, the destructor shall be defined as virtual to make sure to delete a base class pointer to derived class.
  17. The keywords virtual must be present in declaration of class when you import one or more virtual function in class. But it shall not appear in definition of function.
  18. The declaration of virtual function in derived class must be totally matching with counterpart in base class, including parameter, return type, const.One exception is that the return type could be a type of derived class from base class.
  19. virtual DerivedClass* Clone() const { new DerivedClass( *this); }
  20. In some case, the virtual function could be called by static binding in compiling-time in order to get more efficiency. BaseClass::virtualFunction();
  21. There are two and only two strict conditions to make a virtual function call as dynamic binding:
    1. the function must be declared as virtual;
    2. must be called though pointer or reference.
  22. 当通过NotQuery 对象调用NotQuery 析构函数时,它的访问级别是public. 但是当通过Query 指针或引用来调用析构函数时它是protected ,即虚拟函数承接了调用者所属类类型的访问级别.
  23. The virtual function can be invoked only in static binding way in constructor and destructor.
  24. The right of access to virtual function is independent of its definition, that means access to virtual function can be changed in derived class.
  25. Under multiply inheritance, the object or reference or pointer to derived class also can be up-casted to pointer or reference to base class.
  26. 在多继承下二义转换的可能性非常大. Extern void display( const Baseclass1 *p);  extern void display( const BaseClass2 *p);
  27. The call of display( pointerToDerivedClassFromBaseClass1AndBaseClass2 ) will give rise to ambiguity.
  28. 当用Panda (derived from Bear, Endangered) 类对象的地址初始化或赋值Bear 或ZooAnimal 指针或引用时, Panda 接口中Panda 特有的部分以及Endangered 部分就都不能再被访问例。
  29. Static Type and Runtime Type, The Compiler only knows static type
  30. 析构函数调用顺序与构造函数的顺序相反, Panda 析构函数被通过虚拟机制调用在Panda 析构函数执行之后依次静态调用Endangered Bear 和ZooAnimal 析构函数。
  31. public 派生被称为类型继承(type inheritance)。 派生类是基类的子类型,它改写了基类中所有与类型相关的成员函数,而继承了共享的成员函数。派生类往往反映了一种is-a (是一种关系),它提供了较一般的基类的一种特化。
  32. private 派生被称为实现继承implementation inheritance, 派生类不直接支持基类的公有接口。相反,当它提供自己的公有接口时它希望重用基类的实现
  33. has-a 关系一般由是组合composition 而不是继承来支持实现。组合很容易,只须使一个类成为另一个类的成员。
  34. 如果只是希望简单地重用实现,则按值组合比继承更好。如果希望对象的迟缓型分配,则按引用使用一个指针组合通常是一个不错的设计选择。Handle Class.
  35. In derived class, you can use ‘using BaseClass::_name’ to change the control of access to private or protected member in base class same as private or protected member in derived class in case of private inheritance.
  36. 一种常见的多继承形式是继承一个类的公有接口和第二个类的私有实现。
  37. 第三种派生形式是protected 继承。在protected 继承下,基类的所有公有成员都成为派生类的protected 成员,这意味着它们可以被后来从该类派生的类访问,但是不能在层次结构之外被访问。
  38. Handle Class: 1, used for lazy allocation strategy, give a instance when really need to use this subclass; 2, used for changing object at run-time.
  39. 实际上与基类成员同名的派生类成员隐藏了对基类成员的直接访问。为了访问基类成员,我们必须使用类域操作符来限定修饰它bear.ZooAnimal::ival; 这引导编译器直接在ZooAnimal 类域中查找ival 的声明。
  40. To understand deeply how the underlying compliers works on your source code is most important.
  41. C++语言的解决方案是提供另一种可替代按引用组合的继承机制,虚拟继承virtual inheritance。 在虚拟继承下只有一个共享的基类子对象被继承而无论该基类在派生层次中出现多少次。共享的基类子对象被称为虚拟基类virtual base class, 在虚拟继承下基类子对象的复制及由此而引起的二义性都被消除了。In fact, there is a pointer to virtual base class in object of class. Vptr, vtble, vptr  base class.
  42. 虚拟派生不是基类本身的一个显式特性,而是它与派生类的关系。如前面所说明的,虚拟继承提供了按引用组合,也就是说对于子对象及其非静态成员的访问是间接进行的。这使得在多继承情况下把多个虚拟基类子对象组合成派生类中的一个共享实例,从而提供了必要的灵活性。同时即使一个基类是虚拟的,我们仍然可以通过该基类类型的指针或引用来操纵派生类的对象。( through dynamic_cast< BaseClass *> in RTTI)
  43. 无论虚拟基类出现在继承层次中的哪个位置上,它们都是在非虚拟基类之前被构造。

RTTI

  1. RTTI (运行时刻类型识别 Run-Time Type Identification)允许用指向基类的指针或引用来操纵对象的程序能够获取到这些指针或引用所指对象的实际派生类型。在c++中为了支持RTTI 提供了两个操作符1:dynamic_cast 操作符,它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转换类型,把基类指针转换成派生类指针,或把指向基类的左值转换成派生类的引用,当然只有在保证转换能够成功的情况下才可以。2: typeid 操作符,它指出指针或引用指向的对象的实际派生类型。
  2. If the operand is pointer not object pointed to by pointer, the operator will return the static type of pointer.
  3. 但是, 对于要获得的派生类类型的信息dynamic_cast 和typeid 操作符的操作数的类型, 必须是带有一个或多个虚拟函数的类类型, 即对于带有虚拟函数的类而言, RTTI 操作符是运行时刻的事件,而对于其他类而言它只是编译时刻的事件. The operand only could be pointer or reference, but object.
  4. 与C++支持的其他强制转换不同的是, dynamic_cast 是在运行时刻执行的, 如果指针或左值操作数不能被转换成目标类型则dynamic_cast 将失败(return 0 ).
  5. The operator ‘dynamic_cast’ is used to down cast from pointer to base class to pointer to derived class in safe way. Also known as Safe Downcasting.
  6. 当dynamic_cast 被用来转换引用类型时它会以一种不同的方式报告错误情况.如果一个引用的dynamic_cast 失败则会抛出一个异常 which is ‘bad_cast’ exception.
  7. 当typeid 操作符的操作数是类类型, 但不是带有虚拟函数的类类型时, typeid 操作符会指出操作数的类型而不是底层对象的类型.  Take effect on those classes with virtual functions.
  8. 在实现某些应用程序(比如调试器或数据库程序)时,RTTI 的使用是很有必要的。在这些应用程序中只有在运行时刻通过检查‘与对象的类类型一起存储的RTTI 信息’,我们才能知道对象的类型。但是,我们应该尽量减少使用RTTI ,而尽可能多地使用C++的静态类型系统即编译时刻类型检查,因为它是更加安全有效的。
  9. The time the dynamic_cast is a must is when we want to extend the library without adding new virtual functions, but we still hope to add the new function. Then, we must downcast the pointer to base class into the pointer to derived class for calling the non-virtual function defined in the derived class.
  10. The time the dynamic_cast is a must is when we want use the new features defined in derived class, but not appear in base class.
  11. dynamic_cast< Type & > ( lval): also can downcast from a reference to base class to a reference to derived class, if fails, throw STD:bas_cast exception, if dynamic_cast a pointer, failed with 0
  12. RTTI 提供的第二个操作符是typeid 操作符,它在程序中可用于获取一个表达式的类型。如果表达式是一个类类型并且含有一个或多个虚拟成员函数,则答案会不同于表达式本身的类型。
  13. typeid ( Type & / Type object ): If operand’s type is derived class, and base class has virtual function, this operation will return the real dynamic type of operator. Otherwise, the base class has no virtual function, this operation only return the operator’s static type.
  14. 当typeid 操作符的操作数是类类型,但不是带有虚拟函数的类类型时,typeid 操作符会指出操作数的类型,而不是底层对象的类型。
  15. The type of operand in dynamic_cast and typeid must have one or more virtual functions. That is, for the type of class with virtual functions, RTTI’s operation works at run-time, is dynamic resolve; but for other type of class without virtual functions, operation works at compile-time, is statically resolve, with regard to the static type of operator.
  16. Base * p = new Derived;  typeid ( p ) == typeid( Base*) / True;  typeid ( p ) == typid ( Derived *) / False; typeid ( *p ) == typeid ( Base ) / Flase;  typeid ( *p) == typeid( Derived ) / True;
  17. Note: operand in typeid () shall be type of class, this operation will return the type of operator. If operator is a pointer, it will return the type of pointer not the type of object pointed to by pointer.
  18. typeid( pe )与虚拟函数调用机制不同,这是因为操作数pe 是一个指针,而不是一个类类型。为了要获取到派生类类型,typeid 的操作数必须是一个类类型带有虚拟函数。表达式typeid(pe)指出pe 的类型即指向employee 的指针它与表达式typeid(employee*)相等而其他比较的结果都是false。表达式typeid( *pe)指出pe 指向类型是manager与表达式typeid( manager )相等而其他比较的结果都是false.
  19. typeid 操作符实际上返回一个类型为type_info 的类对象.
  20. type_info类的确切定义是与编译器实现相关的,但是这个类的某些特性对每个C++程序却都是相同的。
  21. In class type_info: 1. there is no default constructor; 2. the copy constructor is private; 3. the assignment operation is private; (so, user can not use type_info to define own object).

Exception Handling

  1. 执行该throw 表达式会发生许多个步骤:
    • 1. throw ClassObject ( initValue) 表达式通过调用类类型ClassObject的构造函数创建一个该类的临时temporary/transient 对象.
    • 2. 创建一个ClassObject类型的异常对象,并传递给异常处理代码,该异常对象是第1步throw 表达式创建的临时对象的拷贝。它通过调用ClassObject类的拷贝构造函数而创建。
    • 3 .在开始查找异常处理代码之前在第1 步中由throw 表达式创建的临时对象被销毁。
  2. If in throw statement the object is created, not pointer, so exception handling always know the actual type of throw object.
  3. throw Object: the class of object must meet: 1. public copy or default constructor; 2. public destructor; 3. could not be abstract class.
  4. 派生类类型的catch 子句必须先出现。这确保了只有在没有其他catch 子句适用时才会进入基类类型的catch 子句。
  5. 为抛出的异常找到catch 子句的过程不像函数重载解析,在函数解析期间,选择最佳可行函数时,要考虑所有在调用点可见的候选函数。在异常处理期间,异常的catch 子句不必是与异常最匹配的catch 子句。被选中的catch 子句是最先匹配到的,即,遇到的第一个可以处理该异常的catch 子句。这就是为什么在catch 子句列表中最特化的catch 子句必须先出现的原因。Order-dependent
  6. catch 子句的异常声明,即关键字catch 后面的括号中的声明与函数参数的声明十分类似。在上一个例子中,异常声明类似于一个按值传递的参数对象。eobj 以异常对象的值的拷贝作为初始值,其方式与用实参值的一个拷贝初始化相应的按值传递的函数参数相同,如同函数参数的情形一样,catch 子句的异常声明也可以被改变成引用声明,那么catch 子句可以直接引用被throw 表达式创建的异常对象(transient object created by exception prcess)而不是创建自己的局部拷贝. 要多用引用。
  7. 类类型的参数应该被声明为引用,以防止大型类对象的不必要的拷贝动作,基于同样的原因,对于类类型的异常,其异常声明最好也被声明为引用。根据异常声明是一个对象还是一个引用,catch 子句的行为会有所不同。
  8. 如果catch 子句重新抛出异常,并把这个异常对象传递给在函数调用链中的上一级catch 子句,那么,在到达最后一个处理该异常的catch 子句之前,异常对象是不能被销毁的。因此,对于一个异常对象,直到该异常的最后一个catch 子句退出时,它才被销毁。
  9. 如果被抛出的异常对象是派生类类型的,并且它被针对基类的catch 子句处理,则catch于句一般不能使用派生类类型的特性。
  10. 可以通过重新设计异常类层次结构来定义虚拟函数,然后再在针对基类Excp 的catch 子句中,通过这些虚拟函数来调用派生类类型中更为特化的成员函数。Only works for pointer or reference.
  11. 记住,catch 子句的异常声明的行为与参数声明十分相似,在进入catch 子句时,因为异常声明在这里声明了一个对象,所以eObj 以异常对象的基类子对象Excp 的一个拷贝作为初始值。eobj 是Excp 类型的对象,而不是pushOnFull 类型的对象
  12. 为了调用派生类对象的虚拟函数异常声明必须声明一个指针或引用例。catch ( BaseClass &p or BaseClass *p), the virtual function can be called correctly only when the declaration of catch is reference or pointer.
  13. 把catch 子句的异常声明声明为引用的另一个原因是,确保能正确地调用与异常类型相关联的虚拟函数。对于指针来说,你必须牢记要删除掉指针(可以用 auto_ptr 解决)。 所以最好还是使用引用。
  14. 当一个异常被抛出时,为了寻找能处理该异常的catch 子句,需要从抛出异常的函数内开始,向上通过嵌套的函数调用链,直到找到该异常的catch 子句为止。在函数调用链中查找catch 子句的过程被称作栈展开stack unwinding。
  15. During stack unwinding, the local object will be destroyed, that means the destructor of object will be called. So if there is object created in heap in this object, user must be sure the heap object will be released in destructor of local object. This look like Auto_ptr.
  16. 异常规范可以为类成员函数指定,与非成员函数一样,成员函数声明的异常规范也是跟在函数参数表的后面。
  17. If the statement throw () follow the member function of class, the member function has a empty exception specification throw(), it indicates that member function guarantees no any exception thrown.
  18. C++标准库中的bad_alloc 类被定义成:它的所有成员函数都有一个空的异常规范throw(), 这表示它的成员函数保证不会抛出任何异常。
  19. 如果一个成员函数被声明为const 或volatile 成员函数,如上个例子中的what(), 则异常规范跟在函数声明的const 和volatile 限定修饰符之后。
  20. 同一个函数所有声明中的异常规范都必须指定相同的类型。对于成员函数,如果该函数被定义在类定义之外,则其定义所指定的异常规范,必须与类定义中该成员函数声明中的异常规范相同。派生类的成员函数也必须和基类的成员函数有完全相同的异常规范,或者更严格的异常规范,如果你需要覆盖那个函数。
  21. 基类中虚拟函数的异常规范可以与派生类改写的成员函数的异常规范不同,但是派生类虚拟函数的异常规范必须与基类虚拟函数的异常规范一样或者更严格。
  22. 因为这可以确保当派生类的虚拟函数被通过基类类型的指针调用时,该调用保证不会违背基类成员函数的异常规范。For people who use pointer to the base class, they just see the base class, they don’t know what the derived class is like. So the exception specification on derived class shall be stricter than defined in base class.
  23. 在被抛出的异常的类型与异常规范中指定的类型之间不允许进行类型转换。这个规则有一个小小的例外,当异常规范指定一个类类型或类类型的指针时,如果一个异常规范指定了一个类,则该函数可以抛出从该类公有派生的类类型的异常对象。对指针来说也是类似的,如果异常规范指定了一个类的指针则该函数可以抛出从该类公有派生的类的指针类型的异常对象。
  24. 如果你通过引用捕获异常(catch by reference),你就能避开上述所有问题,不会为是否删除异常对象而烦恼;能够避开slicing异常对象;能够捕获标准异常类型;减少异常对象需要被拷贝的数目。所以你还在等什么?通过引用捕获异常吧(Catch exceptions by reference)!
原文地址:https://www.cnblogs.com/iiiDragon/p/3258980.html