RTTI和CAST

什么是RTTI

  • Run Time Type Information
  • RTTI是一种可以在运行时推导出对象类型的机制
  • 它可以在运行时暴露有关于对象类型的信息,并且仅仅适用于至少有一个虚函数的类。

一、 typeinfo and type_id

众所周知,面向对象的三大特征是封装、继承、多态。

多态指的是一个基类类型的指针,可以指向其派生类对象,例如Base *ptr = new Derive()

在这种情况下,我们如何才能知道ptr指向得对象的类型信息?这就是C++中typeid存在的原因了

注意typeid(expr)expr一定是一个包含虚函数的类

#include <iostream>
#include <typeinfo>

struct Base {
  virtual void vvfunc() {}
};

struct Derived : public Base {};

using namespace std;

int main(int argc, char *argv[]) {
  Derived *pd = new Derived;
  Base *pb = pd;
  cout << typeid(pb).name() << endl;		// P4Base
  cout << typeid(*pb).name() << endl;		// 7Derived
  cout << typeid(pd).name() << endl;		// P7Derived
  cout << typeid(*pd).name() << endl;		// 7Derived
  delete pd;
  return 0;
}

P表示“指针”, 数字7或者4代表乐了名的长度。

#include <cassert>
#include <cstring>
#include <iostream>
#include <typeinfo>
using namespace std;
class A {
private:
  int a;
};

class B : public A {
public:
  virtual void f() {}
};

class C : public B {
public:
  virtual void f() {}
};
int main() {
  A *pa = new B();
  B *pb = new C();

  assert(strcmp(typeid(*pa).name(), "1A") == 0);
  assert(strcmp(typeid(*pb).name(), "1C") == 0);
}

因为A不是多态类型(没有虚函数),所以typeid(*pa)返回的是A的类型信息,而不是B的类型信息。
此外,我们还可以对静态类型执行typeid。表达式的静态类型是指编译时已知的表达式类型。

#include <typeinfo>
int main()
{
    typeid(int) == typeid(int&); // evaluates to true
}

typeid()也可以用于模板类型的推导:

#include <typeinfo>
template <typename T>
T max(T arg1, T arg2) {
    cout << typeid(T).name() << "s compared." << endl;
    return ( arg1 > arg2 ? arg1 : arg2 );
}

二、 dynamic_cast

C语言里面我们可以随便转化任意类型的指针,在C++中,dynamic_cast能够帮助我们进行一些运行时的多态类型检查。如果转化是不安全的,就会抛出bad_cast异常或者返回空指针。

转化引用失败的话会抛出异常,转化指针失败的话会返回nullptr

#include <iostream> // std::cout
#include <typeinfo> // std::bad_cast

class Base { virtual void member() {} };
class Derived : Base {};

int main() {
    try {
        Base b;
        Derived &rd = dynamic_cast<Derived &>(b);
    } catch (std::bad_cast &bc) {
        std::cerr << "bad_cast caught: " << bc.what() << '\n'
    }
    return 0;
}

输出:

bad_cast caught: std::bad_cast

2.1 经典使用场景

下面是一个典型的dynamic_cast的应用案例:

#include <cassert>
#include <iostream>
#include <stdio.h>

struct A {
  virtual void test() { printf("in A\n"); }
};

struct B : A {
  virtual void test() { printf("in B\n"); }

  void test2() { printf("test2 in B\n"); }
};

struct C : B {
  virtual void test() { printf("in C\n"); }

  void test2() { printf("test2 in C\n"); }
};

void Globaltest(A &a) {
  try {
    C &c = dynamic_cast<C &>(a);
    printf("in GlobalTest\n");
  } catch (std::bad_cast) {
    printf("Can't cast to C\n");
  }
}

int main() {
  A *pa = new C;
  A *pa2 = new B;

  pa->test();  // in C
  pa2->test(); // in B

  B *pb = dynamic_cast<B *>(pa);
  if (pb)
    pb->test2(); // test2 in B

  /* pc is nullptr, since the object B knows nothing about C,
   * even if this object is pointed by `A*`.
   */
  C *pc = dynamic_cast<C *>(pa2);
  assert(pc == nullptr);

  C ConStack;
  Globaltest(ConStack); // in GlobalTest

  /* will fail because B knows nothing about C */
  B BonStack;
  Globaltest(BonStack); // Can't cast to C
}

三、 const_cast

const_cast主要用于移除constvolatile等修饰符号。

#include <boost/type_index.hpp>
#include <iostream>
using namespace std;
using boost::typeindex::type_id_with_cvr;

int main() {
  const int *p = new int[1];
  cout << type_id_with_cvr<decltype(p)>().pretty_name() << endl;			// const int*
  cout << type_id_with_cvr<decltype(const_cast<int*>(p))>().pretty_name() << endl;	// int*
  delete[] p;
}

在类中还可以这样

#include <boost/type_index.hpp>
#include <iostream>

using boost::typeindex::type_id_with_cvr;
using namespace std;
class CCTest {
public:
  void printType() const {
    cout << type_id_with_cvr<decltype(this)>().pretty_name() << endl; // const CCTest *
    cout << type_id_with_cvr<decltype(const_cast<CCTest *>(this))>().pretty_name()<< endl; // CCTest *
  }

private:
  int number;
};

int main() {
  CCTest X;
  X.printType();
}

四、 reinterpret_cast

reinterpret_cast主要用于没有血缘关系的类型指针之间进行转化, 主要用于内存空间的转化, 比如将一个内存页转化为整型数组

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

// Returns a hash code based on an address
unsigned short Hash(void *p) {
  uint64_t val = reinterpret_cast<uint64_t>(p);
  return (unsigned short)(val ^ (val >> 16));
}

using namespace std;
int main() {
  int a[20];
  for (int i = 0; i < 20; i++)
    cout << Hash(a + i) << endl;
}

五、 static_cast

static_cast使用的是c++默认的类型转化, 可以满足派生类向基础类的转化, 但不进行类型检查, 所以存在一定的风险,如果没有RTTI就只能够使用static_cast进行多态转化。但是static_cast不能够用于没有血缘关系的类型指针之间转化。static_cast更像是补充的作用,前面几种作用不到的场景,static_cast来做。

int a = 1;
float f = static_cast<float>(a);		// ok
int* ip = &a;
float *if = static_cast<float*>(ip);			// error

多态类转化:

#include <iostream>
using namespace std;
class B {
  virtual void f() {}
};

class D : public B {
public:
  int val = 123;
  void f2() { printf("D::f2(), val = %d\n", val); }
};

void f(B *pb, D *pd) {
  D *pd2 = static_cast<D *>(pb); // Not safe, D can have fields and methods that are not in B.

  pd2->f2();      // val is an uncertain value
  pd2->val = 233; // this may damage other data

  B *pb2 = static_cast<B *>(pd); // Safe conversion, D always contains all of B.
}

int main() {
  B b;
  D d;
  f(&b, &d);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值