C++ Primer 学习笔记_101_特殊工具与技术 --运行时类型识别

h2.western { font-family: "Liberation Sans",sans-serif; font-size: 16pt; }h2.cjk { font-family: "微软雅黑"; font-size: 16pt; }h2.ctl { font-family: "AR PL UMing CN"; font-size: 16pt; }h1 { margin-bottom: 0.21cm; }h1.western { font-family: "Liberation Sans",sans-serif; font-size: 18pt; }h1.cjk { font-family: "微软雅黑"; font-size: 18pt; }h1.ctl { font-family: "AR PL UMing CN"; font-size: 18pt; }p { margin-bottom: 0.25cm; line-height: 120%; }

特殊工具与技术

--运行时类型识别

引:

通过下面两个操作符提供RTTI:

1.typeid操作符,返回指针或引用所指对象的实际类型。

2.dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

对于带虚函数的类,在运行时执行RTTI操作符,但对于其他类型,在编译时计算RTTI操作符。

当具有基类的引用或指针,但需要执行不是基类组成部分的派生类操作的时候,需要动态的强制类型转换。通常,从基类指针获得派生类行为最好的方法是通过虚函数。当使用虚函数的时候,编译器自动根据对象的实际类型选择正确的函数。

但是,在某些情况下,不可能使用虚函数。在这些情况下,RTTI提供了可选的机制。然而,这种机制比使用虚函数更容易出错:程序员必须知道应该将对象强制转换为哪种类型,并且必须检查转换是否成功执行了

一、dynamic_cast操作符

可以使用dynamic_cast操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。与dynamic_cast一起使用的指针必须是有效的—— 它必须为 0或者指向一个对象。

与其他强制类型转换不同,dynamic_cast涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型的对象,则dynamic_cast失败。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果是 0值;如果转换到引用类型的dynamic_cast失败,则抛出一个bad_cast类型的异常。

1、使用dynamic_cast操作符

    if (Derived *derivedPtr = dynamic_cast<Derived *>(basePtr))
    {
        //...
    }
    else
    {
        //...
    }

p { margin-bottom: 0.25cm; line-height: 120%; }

解释:在运行时,如果basePtr实际指向Derived对象,则转换将成功,并且derivedPtr将被初始化为指向basePtr所指的Derived对象;否则,转换的结果是0,意味着将derivedPtr置为0,并且if中的条件失败。同时也可以对值为 0的指针应用dynamic_cast,这样做的结果是0。

【最佳实践】

在条件中执行dynamic_cast保证了转换和其转换结果测试在一个表达式中进行!

2、使用dynamic_cast和引用类型

形式:

    dynamic_cast<Type &>(val);

p { margin-bottom: 0.25cm; line-height: 120%; }

只有val实际引用的是一个Type类型对象,或者val是一个Type派生类型的对象的时候,dynamic_cast操作才将操作数val转换为Type&类型。

因为不存在空引用,所以不可能对引用使用用于指针强制类型转换的检查策略,相反,当转换失败的时候,它抛出一个std::bad_cast异常,该异常在库头文件typeinfo中定义。

void f(const Base &b)
{
    try
    {
        const Derived &d = dynamic_cast<Derived &>(b);
    }
    catch(std::bad_cast)
    {

    }
}

//P648 习题18.13-15
//1)
class A
{
public:
    virtual void func()
    {}
};
class B : public A {};
class C : public B {};
class D : public B,public A {};

int main()
{
    C c;
    A *pa = &c;
    if (C *pc = dynamic_cast<C *>(pa))
    {
        cout << "dynamic_cast success" << endl;
    }
    else
    {
        cout << "dynamic_cast fail" << endl;
    }
}

//2)
    try
    {
        C &temp = dynamic_cast<C &>(*pa);
    }
    catch(std::bad_cast &e)
    {
        cout << e.what() << endl;
    }

//18.16 什么时候可以使用dynamic_cast代替虚函数
/**答:
*当我们需要在派生类中增加函数,但又不能在基类增加虚函数时
*就可以使用dynamic_cast代替 虚函数
*/

p { margin-bottom: 0.25cm; line-height: 120%; }

二、typeid操作符

typeid操作符使程序能够问一个表达式:你是什么类型?

	  typeid(e)

p { margin-bottom: 0.25cm; line-height: 120%; }

如果表达式的类型是类类型且该类包含一个或多个虚函数,则表达式的动态类型可能不同于它的静态编译时类型。例如,如果表达式对基类指针解引用,则该表达式的静态编译时类型是基类类型;但是,如果指针实际指向派生类对象,则 typeid操作符将说表达式的类型是派生类型。

typeid操作符可以与任何类型的表达式一起使用。内置类型的表达式以及常量都可以用作typeid操作符的操作数。如果操作数不是类类型或者是没有虚函数的类,则typeid操作符指出操作数的静态类型;如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型。

typeid操作符的结果是名为type_info的标准库类型的对象引用,要使用type_info类,必须包含库头文件typeinfo。


使用typeid操作符

typeid最常见的用途是比较两个表达式的类型,或将表达式的类型与特定类型相比较:

    Base *pb;
    Derived *pd;

    if (typeid(*pb) == typeid(*pd))
    {
        cout << "typeid(*pb) = typeid(*pd)" << endl;
    }

    if (typeid(pb) == typeid(pd))
    {
        cout << "typeid(pb) = typeid(pd)" << endl;
    }

    if (typeid(*pb) == typeid(Derived))
    {
        cout << "typeid(*pb) = typeid(Base)" << endl;
    }

    int a;
    if (typeid(int) == typeid(a))
    {
        cout << "typeid(int) = typeid(a)" << endl;
    }

p { margin-bottom: 0.25cm; line-height: 120%; }

【小心地雷】

只有当typeid的操作数是带虚函数的类类型的对象的时候,才返回动态类型信息。测试指针(相当于指针指向的对象)返回指针的静态的、编译时类型。

    Derived d;
    Base *pb = &d;

    if (typeid(*pb) == typeid(Derived))
    {
        cout << "typeid(*pb) = typeid(Base)" << endl;
    }

p { margin-bottom: 0.25cm; line-height: 120%; }

如果指针pb的值是 0,那么,如果pb的类型是带虚函数的类型,则typeid(*p)抛出一个bad_typeid异常;如果p的类型没有定义任何虚函数,则结果与 p的值是不相关的。

//P650 习题18.17
    AndQuery a;
    Query_base *qb1 = &a,*qb2;

    if (dynamic_cast<AndQuery *>(qb1))
        cout << "success" << endl;
    else
        cout << "failure" << endl;

    if (dynamic_cast<AndQuery *>(qb2))
        cout << "success" << endl;
    else
        cout << "failure" << endl;

// 习题18.18
    try
    {
        dynamic_cast<AndQuery &>(*qb1);
        cout << "success" << endl;
    }
    catch(std::bad_cast)
    {
        cout << "failure" << endl;
    }

    try
    {
        dynamic_cast<AndQuery &>(*qb2);
        cout << "success" << endl;
    }
    catch(std::bad_cast)
    {
        cout << "failure" << endl;
    }

//习题18.19
    AndQuery a;
    Query_base *qb1 = &a,*qb2;

    try
    {
        dynamic_cast<AndQuery &>(*qb1);

        if (typeid(*qb1) == typeid(AndQuery))
        {
            cout << "Same" << endl;
        }
        else
        {
            cout << "Not Same" << endl;
        }
    }
    catch(std::bad_cast)
    {
        cout << "failure" << endl;
    }

    if (typeid(*qb1) == typeid(*qb2))
    {
        cout << "Same" << endl;
    }
    else
    {
        cout << "Not Same" << endl;
    }

时间: 2024-09-30 15:38:02

C++ Primer 学习笔记_101_特殊工具与技术 --运行时类型识别的相关文章

C++ Primer 学习笔记_102_特殊工具与技术 --运行时类型识别[续]

特殊工具与技术 --运行时类型识别[续] 三.RTTI的使用 当比较两个派生类对象的时候,我们希望比较可能特定于派生类的数据成员.如果形参是基类引用,就只能比较基类中出现的成员,我们不能访问在派生类中但不在基类中出现的成员. 因此我们可以使用RTTI,在试图比较不同类型的对象时返回假(false). 我们将定义单个相等操作符.每个类定义一个虚函数 equal,该函数首先将操作数强制转换为正确的类型.如果转换成功,就进行真正的比较:如果转换失败,equal 操作就返回 false. 1.类层次 c

C++ Primer 学习笔记_98_特殊工具与技术 --优化内存分配

特殊工具与技术 --优化内存分配 引言: C++的内存分配是一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象.new表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象. new基于每个对象分配内存的事实可能会对某些类强加不可接受的运行时开销,这样的类可能需要使用用户级的类类型对象分配能够更快一些.这样的类使用的通用策略是,预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象. 另外一些类希望按最小尺寸为自己的数据成员分配需要的内存.例如,

C++ Primer 学习笔记_104_特殊工具与技术 --嵌套类

特殊工具与技术 --嵌套类 可以在另一个类内部(与后面所讲述的局部类不同,嵌套类是在类内部)定义一个类,这样的类是嵌套类,也称为嵌套类型.嵌套类最常用于定义执行类. 嵌套类是独立的类,基本上与它们的外围类不相关,因此,外围类和嵌套类的对象是互相独立的.嵌套类型的对象不具备外围类所定义的成员,同样,外围类的成员也不具备嵌套类所定义的成员. 嵌套类的名字在其外围类的作用域中可见,但在其他类作用域或定义外围类的作用域中不可见.嵌套类的名字将不会与另一作用域中声明的名字冲突 嵌套类可以具有与非嵌套类相同

C++ Primer 学习笔记_99_特殊工具与技术 --优化内存分配[续1]

特殊工具与技术 --优化内存分配[续1] 三.operator new函数和operator delete 函数 – 分配但不初始化内存 首先,需要对new和delete表达式怎样工作有更多的理解.当使用new表达式 string *sp = new string("initialized"); 的时候,实际上发生三个步骤: 1)首先,表达式调用名为operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象; 2)接下来,运行该类型的一个构造函数

C++ Primer 学习笔记_105_特殊工具与技术 --联合:节省空间的类

特殊工具与技术 --联合:节省空间的类 联合是一种特殊的类.一个 union 对象可以有多个数据成员,但在任何时刻,只有一个成员可以有值.当将一个值赋给 union 对象的一个成员的时候,其他所有都变为未定义的. 为 union 对象分配的存储的量至少与包含其最大数据成员的一样多.联合提供了便利的办法表示一组相互排斥的值,这些值可以是不同类型的. 1.定义联合 作为例子,我们可能有一个处理不同各类数值或字符数据的过程.该过程可以定义一个 union 来保存这些值: union ToKenValu

C++ Primer 学习笔记_106_特殊工具与技术 --局部类

特殊工具与技术 --局部类 可以在函数体内部定义类,这样的类称为局部类.一个局部类定义了一个类型,该类型只在定义它的局部作用域中可见.与嵌套类不同,局部类的成员是严格受限的. 局部类的所有成员(包括函数)必须完全定义在类定义体内部,因此,局部类远不如嵌套类有用. 实际上,成员完全定义在类中的要求限制了局部类成员函数的复杂性.局部类中的函数很少超过数行代码,超过的话,阅读者会难以理解代码. 类似地,不允许局部类声明 static 数据成员,没有办法定义它们. 1.局部类不能使用函数作用域中的变量

C++ Primer 学习笔记_107_特殊工具与技术 --固有的不可移植的特征[上]

特殊工具与技术 --固有的不可移植的特征[上] C++从 C 语言继承来的不可移植特征:位域和 volatile 限定符.这些特征可使与硬件接口的直接通信更容易. C++ 还增加了另一个不可移植特征(从 C 语言继承来的):链接指示,它使得可以链接到用其他语言编写的程序. 一.位域 可以声明一种特殊的类数据成员,称为位域,来保存特定的位数.当程序需要将二进制数据传递给另一程序或硬件设备的时候,通常使用位域. 位域在内存中的布局是机器相关的. 位域必须是整型数据类型,可以是 signed 或 un

C++ Primer 学习笔记_100_特殊工具与技术 --优化内存分配[续2]

特殊工具与技术 --优化内存分配[续2] 七.一个内存分配器基类 预先分配一块原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造:释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统.这种策略常被称为维持一个自由列表.可以将自由列表实现为已分配但未构造的对象的链表. 我们将定义一个名为 CachedObj 的新类来处理自由列表.像 QueueItem 这样希望优化其对象分配的类可以使用 CachedObj 类,而不用直接实现自己的 new 和 del

C++ Primer 学习笔记_103_特殊工具与技术 --类成员指针

特殊工具与技术 --类成员指针 成员指针可以做到:获得特定成员的指针,然后从一个对象或别的对象获得该成员.成员指针应该包含类的类型以及成员的类型. 一.声明成员指针 测试类: class Screen { public: typedef std::string::size_type index; char get() const; char get(index ht,index wd) const; private: std::string contents; index cursor; ind