C + + type prediction - RTTI

        C + + does not have the type prediction function of instanceof similar to Java language. What methods can achieve the type prediction function similar to Java? Compared with Java, C + + can only obtain runtime type information through RTTI (Run Time Type Identification) mechanism, and the final code generated by C + + is directly related to the machine (that is, different compilers implement RTTI differently, and the C + + standard only makes a contract).

        RTTI provides two operators  : typeid and dynamic_cast

         typeid: returns the actual type indicated by the pointer and reference

         dynamic_cast: safely converts a pointer or reference of a base class type to a pointer or reference of its derived class type

         The dynamic polymorphism of C + + is realized by virtual functions. For polymorphic objects, the type of objects cannot be determined in the program compilation stage. When a class contains a virtual function, the pointer of its base class can point to the object of any derived class. At this time, it may not know which object the pointer of the base class points to. The determination of type should be made by using the runtime type identification at runtime.

1,typeid

         To get the type of an object, you can use   typeid operation, which returns a   type_info   Reference to class object, type_ The info class is defined in the file < TypeInfo >.

         Let's introduce type first_ Info source code:   

  class type_info
  {
  public:
    /** Destructor first. Being the first non-inline virtual function, this
     *  controls in which translation unit the vtable is emitted. The
     *  compiler makes use of that information to know where to emit
     *  the runtime-mandated type_info structures in the new-abi.  */
    virtual ~type_info();

    /** Returns an @e implementation-defined byte string; this is not
     *  portable between compilers!  */
    const char* name() const _GLIBCXX_NOEXCEPT
    { return __name[0] == '*' ? __name + 1 : __name; }

#if !__GXX_TYPEINFO_EQUALITY_INLINE
    // In old abi, or when weak symbols are not supported, there can
    // be multiple instances of a type_info object for one
    // type. Uniqueness must use the _name value, not object address.
    bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT;
    bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT;
#else
  #if !__GXX_MERGED_TYPEINFO_NAMES
    /** Returns true if @c *this precedes @c __arg in the implementation's
     *  collation order.  */
    // Even with the new abi, on systems that support dlopen
    // we can run into cases where type_info names aren't merged,
    // so we still need to do string comparison.
    bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT
    { return (__name[0] == '*' && __arg.__name[0] == '*')
	? __name < __arg.__name
	: __builtin_strcmp (__name, __arg.__name) < 0; }

    bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
    {
      return ((__name == __arg.__name)
	      || (__name[0] != '*' &&
		  __builtin_strcmp (__name, __arg.__name) == 0));
    }
  #else
    // On some targets we can rely on type_info's NTBS being unique,
    // and therefore address comparisons are sufficient.
    bool before(const type_info& __arg) const _GLIBCXX_NOEXCEPT
    { return __name < __arg.__name; }

    bool operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
    { return __name == __arg.__name; }
  #endif
#endif

#if __cpp_impl_three_way_comparison < 201907L
    bool operator!=(const type_info& __arg) const _GLIBCXX_NOEXCEPT
    { return !operator==(__arg); }
#endif

#if __cplusplus >= 201103L
    size_t hash_code() const noexcept
    {
#  if !__GXX_MERGED_TYPEINFO_NAMES
      return _Hash_bytes(name(), __builtin_strlen(name()),
			 static_cast<size_t>(0xc70f6907UL));
#  else
      return reinterpret_cast<size_t>(__name);
#  endif
    }
#endif // C++11

    // Return true if this is a pointer type of some kind
    virtual bool __is_pointer_p() const;

    // Return true if this is a function type
    virtual bool __is_function_p() const;

    // Try and catch a thrown type. Store an adjusted pointer to the
    // caught type in THR_OBJ. If THR_TYPE is not a pointer type, then
    // THR_OBJ points to the thrown object. If THR_TYPE is a pointer
    // type, then THR_OBJ is the pointer itself. OUTER indicates the
    // number of outer pointers, and whether they were const
    // qualified.
    virtual bool __do_catch(const type_info *__thr_type, void **__thr_obj,
			    unsigned __outer) const;

    // Internally used during catch matching
    virtual bool __do_upcast(const __cxxabiv1::__class_type_info *__target,
			     void **__obj_ptr) const;

  protected:
    const char *__name;

    explicit type_info(const char *__n): __name(__n) { }

  private:
    /// Assigning type_info is not supported.
    type_info& operator=(const type_info&);
    type_info(const type_info&);
  };

          You can see the type_ The constructors / copy constructs of info class are all private functions, that is, users are not allowed to create their own type_info class object. The methods that this class often uses to judge types are operator() = = and operator()! =.

        The following test code demonstrates how to use typeid. Note: type inference must be realized with the help of virtual function table!

#include <iostream>
#include <functional>
using namespace std;

class Base{
public:
    virtual 
	void test(){
		cout << "Base::test" << endl;
	}
};

class Derived : public Base{
public:
    void test(){
		cout << "Derived::test" << endl;
	}

	virtual 
	~Derived(){
		cout << "Derived::~Derived" << endl;
	}
};


int main()
{
    Base base;
    Derived derive;
	Derived derive2;
    
	Base* pBase = new Base();
	Base* pBase2 = new Derived();
	Derived* pDerive = new Derived();
	
    //base and derive types are different, and false is returned
    cout << "typeid(base) == typeid(derive) : " << (typeid(base) == typeid(derive)) << endl;

	//Derive and derive2 are of the same type and return true
    cout << "typeid(derive2) == typeid(derive) : " << (typeid(derive2) == typeid(derive)) << endl;
	
	//base and derive types are different, and false is returned
    cout << "typeid(&base) == typeid(&derive) : " << (typeid(&base) == typeid(&derive)) << endl;
	
	//Derive and derive2 are of the same type and return true
    cout << "typeid(&derive2) == typeid(&derive) : " << (typeid(&derive2) == typeid(&derive)) << endl;

	//pBase and pdrive point to different object types and return false
	cout << "typeid(pBase) == typeid(pDerive) : " << (typeid(pBase) == typeid(pDerive)) << endl;
	
	//pBase2 and pdrive point to the same object type and return true
    cout << "typeid(pBase2) == typeid(pDerive) : " << (typeid(*pBase2) == typeid(*pDerive)) << endl;

    return 0;
}

        You can test the following, comment out the virtual keyword and try again. The type inference result will be different! The following is the output information:

With virtual functions:
typeid(base) == typeid(derive) : 0
typeid(derive2) == typeid(derive) : 1
typeid(&base) == typeid(&derive) : 0
typeid(&derive2) == typeid(&derive) : 1
typeid(pBase) == typeid(pDerive) : 0
typeid(pBase2) == typeid(pDerive) : 1


Without virtual functions:
typeid(base) == typeid(derive) : 0
typeid(derive2) == typeid(derive) : 1
typeid(&base) == typeid(&derive) : 0
typeid(&derive2) == typeid(&derive) : 1
typeid(pBase) == typeid(pDerive) : 0
typeid(pBase2) == typeid(pDerive) : 0

        Why is this happening???

        Did we mention that virtual is a means to realize dynamic polymorphism? If there is no virtual function table, the type information can be deduced according to the operation content during compilation. The following static types can determine the type information at the compilation stage:

  1. Type name (for example: int, Base)
  2. A basic type of variable (for example: 2, 3.0f)
  3. A specific object (for example: base;)
  4. A pointer to a class object without a virtual function table

        So when you compare typeid(pBase) and typeid(pBase2)   During type derivation, since there is no virtual function table in the Base class, it is actually the same as   typeid(Base *) has the same effect! That is, the type of operand can be determined at compile time!

        So how does typeid dynamically derive types? That must have something to do with our virtual function table! As we mentioned earlier, typeid returns type_ The reference of info class object, that is, as long as it is an instance of the same class, the return value of typeid should be the same!

	//The return addresses of typeid(pBase2) and typeID (pdrive) are the same
    cout << "typeid(pBase2) = " << &typeid(*pBase2) <<  " typeid(pDerive) = "<< &typeid(*pDerive) << endl;

        Add the above test code to the main function (note to cancel the comment on the virtual keyword in the Base class because we want to test the return address during dynamic type derivation). At this time, the log information output by the test code is:

typeid(pBase2) = 0x564006a8ad20 typeid(pDerive) = 0x564006a8ad20

typeid returns the same address, so if you compare the types of the two objects, they must be the same!

Note: at this time, you comment out virtual to see if it is still the same? Think about why?

          Let's talk about the principle of typeid first. If you are a C + + compiler developer and let you implement RTTI technology, how do you determine the real type of an object?

        First, each class should have a type after compilation_ Info data structure to identify the uniqueness of this class, this type_info data should be deterministic and created at compile time.

        Secondly, the implementation of typeid; The function of displaying typeid considers two cases:

  1.   Static: directly find the type of the corresponding type according to the operands (such as int, class object without virtual function table, etc.)_ Info data.
  2.   Dynamic: at this time, you can only use the virtual function table to return the real type of the object. Then, the type of the real type should be found in the virtual function table_ The info data entry (similar to the virtual function table) stores the address of the virtual function, which should also be stored here, pointing to type_info (address of the data).

         ok, at this point, we need to deeply mine the knowledge related to virtual function table, so you need to supplement the knowledge related to virtual function table first. Let's talk about the relationship between the dynamic type derivation of typeid and the virtual function table in the next article!

         Tips: you can take a look at my new[]/delete [], the principle is the same!

2,dynamic_cast

         dynamic_cast operator converts a pointer to the base class into a pointer to the derived class; Null pointer if failed. Note that it is mainly used for up conversion, that is, from the parent class pointer to the child class pointer (used to realize c + + polymorphism), because the child class pointer can be assigned to the parent class pointer without conversion!

        Well, since we want to achieve polymorphism, we must use the virtual function table! That is, dynamic_cast is not allowed to operate on objects without virtual function table( Throw exception / compile error)

        Add the following test code:

	//Convert parent class pointer to child class pointer
	auto p1 = dynamic_cast<Derived*>(pBase);
	
	//Converts a parent pointer to a subclass to a subclass pointer
	auto p2 = dynamic_cast<Derived*>(pBase2);
	
	cout << "p1 = " << p1 << " p2 = " << p2 << endl;
    

Log output:

p1 = 0 p2 = 0x556f565f4ed0

         

         So far, we have explained the use and principle of two operators in RTTI technology in C + +! In the following article, we will further explain the relationship between typeid and virtual function.

        If you have any questions, please leave a message, welcome to forward, do not copy!

Tags: C++

Posted on Tue, 07 Sep 2021 17:23:48 -0400 by danoli3