zoukankan      html  css  js  c++  java
  • C++ Primer学习笔记 运行时类型识别

    运行时类型识别(run-time type idenfitication,RTTI)的功能由2个运算符实现:
    1)typeid,用于返回表达式的类型;
    2)dynamic_cast,用于将base class pointer(或reference)安全转型为derived class pointer(或reference)。

    TIPS:使用RTII时,应尽可能定义virtual函数,而非non-virtual函数。当调用virtual函数时,编译器会根据对象的动态类型自动地选择正确的函数版本。

    dynamic_cast运算符

    使用方法

    // type必须是类型
    dynamic_cast<type*>(e) // e必须是一个指针
    dynamic_cast<type&>(e) // e必须是左值
    dynamic_cast<type&&>(e) // e是右值
    

    当e的类型满足以下3种条件之一时:1)是type本身,2)public base class,3)public derived class时,则转型可以成功;否则,转型失败。
    当转换失败时,如果转换目标是指针,结果为0;如果转换目标是引用,则抛出bad_cast异常。

    注:不能是private base/derived class,因为private继承代表has-a关系,而非public继承的is-a关系。

    dynamic_cast指针转型

    对于有public继承关系Base class和Derived class

    class Base {
    public:
    	virtual void func1() { cout << "Base::func1()" << endl; }
    	void func2() { cout << "Base::func2()" << endl; }
    };
    
    class Derived : public Base {
    public:
    	virtual void func1() { cout << "Derived::func1()" << endl; }
    	void func2() { cout << "Derived::func2()" << endl; }
    };
    

    使用dynamic_cast这样对指针进行转型:

    // 转型成功
    Base* bp = new Derived;
    if (Derived *dp = dynamic_cast<Derived*>(bp)) { // 转型
    	// 使用dp指向Derived对象
    	cout << "dp is not null" << endl; // 打印"dp is not null"
    }
    else {
    	// bp指向base对象
    	cout << "dp is null" << endl;
    }
    
    // 转型失败
    Base* bp2 = new Base;
    if (Derived *dp = dynamic_cast<Derived*>(bp2)) { // 转型
    	// 使用dp指向Derived对象
    	cout << "dp is not null" << endl;
    }
    else {
    	// bp指向base对象
    	cout << "dp is null" << endl;  // 打印"dp is null"
    }
    

    注:对空指针进行dynamic_cast转型,结果是所需类型的空指针。

    dynamic_cast引用转型

    与指针转型不同的是,引用不存在空引用的情况,转型失败时直接抛出std::bad_cast异常(定义在)。

    void func(const Base &b)
    {
    	try {
    		const Derived &d = dynamic_cast<const Derived&>(b); // 转型,注意dynami_cast转型无法丢掉const常量性
    		cout << "b is Derived object" << endl;
    	}
    	catch (bad_cast) {
    		cout << "b is Base object" << endl;
    	}
    }
    
    int main()
    {
    	Base b;
    	func(b); // 打印"b is Base object"
    
    	Derived d;
    	func(d); // 打印"b is Derived object"
    	return 0;
    }
    

    [======]

    typeid运算符

    typeid运算符 运行程序向表达式提问:你的对象是什么类型?
    typeid用法

    typeid(e); // 运算符对象e,可以是任意表达式或类型的名字
    // typeid 操作符结果是一个常量对象的引用,对象的类型是标准库类型type_info或其public派生类
    

    typeid运算符的对象e可用是任意表达式,或者类型的名字,顶层const会被忽略。
    如果e是引用,typeid返回所引用对象的类型;
    如果e是数组或函数时,不会执行指针的标准类型转换,仍然是数组类型或者函数类型,而非指针类型;
    如果e不属于类类型或者是一个不包含任何virtual函数的类时,typeid运算符指示的运算对象是静态类型。只有当运算符对象是 定义了至少一个virtual函数的类的左值对象时,typeid的结果直到运行时才会求得;否则,编译期就返回静态类型。

    typeid的应用

    typeid常用于比较2个表达式类型是否相同,或者比较一条表达式是否与指定类型相同

    class Base {
    public:
    	virtual void func1() { cout << "Base::func1()" << endl; } // 包含了1个virtual函数
    	void func2() { cout << "Base::func2()" << endl; }
    };
    
    class Derived : public Base {
    public:
    	virtual void func1() { cout << "Derived::func1()" << endl; }
    	void func2() { cout << "Derived::func2()" << endl; }
    };
    
    int main()
    {
    	Derived *dp = new Derived;
    	Base *bp = dp;
    	if (typeid(bp) == typeid(dp)) { // 不通过:比较Base*和Derived*,将在编译期返回静态类型
    		cout << "bp, dp own same type" << endl; // 不执行
    	}
    
    	if (typeid(*bp) == typeid(*dp)) { // 通过:比较Base对象和Derived对象,将在运行期求值
    		cout << "*bp, *dp own same type" << endl; // 执行
    	}
    
    	if (typeid(*bp) == typeid(Derived)) { // 通过:比较Base对象和Derived对象,将在运行期求值
    		cout << "*bp, Derived own same type" << endl; // 执行
    	}
    
    	if (typeid(bp) == typeid(Derived)) { // 不通过:比较Base*和Derived,将在编译期返回静态类型
    		cout << "bp, Derived own same type" << endl; // 不执行
    	}
    	return 0;
    }
    

    [======]

    运行时类型识别RTTI技术

    何为两个对象相等?
    对于2个对象,如果它们的\(\color{red}{类型相同}\)并且\(\color{red}{对应的数据成员相同}\),我们则称这2个对象是相等的。
    在继承体系中,除了继承而来的数据成员,派生类还有自己负责添加的数据成员,因此,派生类的相等运算符(operator==)必须把派生类的新成员考虑进来。

    一种判断对象是否相等的方案:定义一套virtual函数,在继承体系上的各个层次上分布执行相等性判断。为此,可以为base class定义一个相等运算符,可以将其工作委托给virtual函数equal。
    上述方案存在的问题:virtual函数的base class版本和derived class版本必须具有相同的形参类型,否则无法通过base pointer/reference动态调用derived object的virtual函数。如果想定义virtual equal函数,参数必须是base class reference,因为base class并不知道derived class的存在,而equal判断是否相等如果用copy对象,就失去了意义。因此其函数参数必须是base class reference或pointer,那为什么是reference而不是pointer呢?因为参数不允许为null,跟null对象比较没有意义。

    思考一个问题:如果两个对象的类型不一样,这2个对象相等吗?
    答案是否定的,肯定不相等。这也就是说,比较2个对象是否相等时,如果排除了类型不同的情况,那么两个对象类型相同,而判断同类型对象是否相等,只需要借助对象的virtual函数equal比较数据成员(自身添加的以及继承而来的数据成员)即可。

    于是,可以写出判断2个对象是否相等函数的示例:

    class Base {
    	// 并没有将operator==定义为成员函数,是因为不必要是成员函数。effective C++条款23告诉我们,这样可以增加类的封装性
    	friend bool operator==(const Base&, const Base&);
    public:
    	// Base的接口成员
    protected:
    	virtual bool equal(const Base&) const;
    private:
    	// Base的数据成员
    };
    
    bool operator==(const Base& lhs, const Base& rhs)
    {
    	// 如果typeid不相同,返回false;否则调用equal比较数据成员
    	return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
    }
    
    class Derived : public Base {
    public:
    	// Derived的接口成员
    protected:
    	virtual bool equal(const Base&) const;
    private:
    	// Derived的数据成员
    };
    
    bool Derived::equal(const Base& rhs) const
    {
    	auto r = dynamic_cast<const Derived&>(rhs);
    
    	// 执行比较2个Derived对象操作并返回结果
    	if (all data == all data of rhs)
    		return true;
    	else return false;
    }
    

    type_info类

    type_info类准确定义不同编译器略有不同。C++标准要求typeinfo类定义在头文件,且至少提供以下操作:

    t1 == t2 如果type_info对象t1,t2是同一种类型,就返回true;否则,返回false
    t1 != t2 如果type_info对象t1, t2不是同一种类型,就返回true;否则,返回false
    t.name() 返回一个C风格字符串,表示类型的名字
    t1.before(t2)

    type_info类一般作为base class出现,当编译器希望提供额外的类型信息时,通常在type_info的derived class中完成。
    可以从type_info的源码中看到,它没有默认构造函数,也删除了copy函数(copy构造、copy assignment运算符)。无法定义或拷贝type_info对象,也不能赋值。创建type_info类对象唯一途径是使用typeid运算符(前面有提到typeid运算符返回 表示表达式类型的值)

    注意:C++ Primer 5th提到,type_info删除了移动构造函数,这点是通过删除private成员__std_type_info_data的移动构造函数实现,而非通过type_info声明自身的移动构造函数为delete。同样的,无法为type_info合成default构造函数,因为private成员__std_type_info_data的default构造函数已经被删除。

    // 以下代码来自VS 2017 type_info定义
    class type_info
    {
    public:
        type_info(const type_info&) = delete;
        type_info& operator=(const type_info&) = delete;
        virtual ~type_info() noexcept;
        ...
    private:
        mutable __std_type_info_data _Data;
    }
    

    type_info的用法

    如何利用type_info类打印类型信息?
    type_info类的name成员函数返回一个C风格字符串,表示对象的类型名称。具体的名称字符串,取决于不同的编译器实现(可查看编译器手册),不一定与程序员编写的类型信息一样。
    例,

    int arr[10];
    Derived d;
    Base* p = &d;
    
    cout << typeid(52).name() << endl;
    cout << typeid(arr).name() << endl;
    cout << typeid(std::string).name() << endl;
    cout << typeid(p).name() << endl;
    cout << typeid(*p).name() << endl;
    

    MSVC 2017运行结果:

    int
    int [10]
    class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
    class Base *
    class Derived
    
  • 相关阅读:
    ScheduledThreadPoolExecutor源码解读
    Spring事务源码阅读笔记
    Spring AOP的实现研究
    cglib之Enhancer
    Spring IOC容器创建bean过程浅析
    CompletionService简讲
    Spring Boot:关于“No converter found for return value of type: class xxx”的解决方法
    java关于Date转换成字符串格式以及Date的增加
    jsp中文乱码六种情况---解决方案
    MYSQL给表添加字段
  • 原文地址:https://www.cnblogs.com/fortunely/p/15627186.html
Copyright © 2011-2022 走看看