编译期检测所有未定义行为和内存泄漏、类型转换、虚函数表、奇异递归模板模式

本文介绍了如何利用constexpr检测编译时的常见未定义行为,如左移限制和数组越界,同时讲解了四种强制类型转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)及其应用场景。此外,还讨论了虚函数表和CRTP(奇异递归模板模式)在动态多态和静态多态中的角色。
摘要由CSDN通过智能技术生成

常见未定义行为(将函数改成constexpr就能在编译时检测出)

左移只能左移0-31位

constexpr int func(int i)
{
  return 1<<i;
}

int main()
{
  constexpr auto _1 = func(-1);
  return 0;
}

数组越界

int a[32]={};
a + 33;//越界了,虽然没使用这个结果 g++不报错,clang报错
//a+32是可以获取但不能访问的,这是为了便于数组遍历。而a+33是不能获取也不能访问的

忘记delete:因此constexpr可以检测是否发生内存泄漏
重复delete
未初始化
push_back导致迭代器失效

4种强制类型转换

  • 静态转换static_cast
    编译时期完成,可以用于基本类型间转换,不能用于基本类型的指针间转换。还可以用于基类与派生类指针之间的转换(上转安全、下转危险)、void*的转换

  • 动态转换dynamic_cast
    运行时期完成,用于带虚函数的基类指针下转型。RTTI信息辅助判断是不是能转,如果不能,得到的结果是空指针。

  • 常量转换const_cast
    去掉表达式的const属性

  • 重解释转换reinterpret_cast
    各种类型指针之间的强转重新解释

#include <iostream>
struct A
{
  virtual void fun(){};
  int m=2;
};
struct B: public A{
  void fun()
  {
    std::cout<<"xcx"<<std::endl;
  }
  int n=3;
};

int main()
{
  //1.const_cast
  //去除const
  const char * a = "nh";
  char* b = const_cast<char*>(a);

  //2.static_cast
  //基本类型转换
  std::string s = static_cast<std::string>(b);
  //类对象转换
  B bb;
  A aa = static_cast<A>(bb);//上转安全
  //类指针转换
  A* aa_ptr = static_cast<A*>(&bb);
  //void*强转
  void * v = &b;
  char* vv = static_cast<char*>(v);

  //3.reinterpret_cast不同类型强转:不能用于constexpr常量表达式。同理,C风格的指针强转也是调用reinterpret_cast,也是不行的
  char cc = 'a';
  char* cc_ptr = &cc;
  int* cc_int = reinterpret_cast<int*>(cc_ptr);
  std::cout<<*cc_int<<std::endl;//609
  //char占一个字节,8位,对应ascill码为97(16进制61),存储为01100001,
  //强转为int后,int占4字节,这串二进制作为低位,高位是后面地址存储的值
  //这里后面地址存储的值是02 00 00,即为00 00 02 61:1001100001,转为10进制就是609
  
  //4.dynamic_cast在运行时将父类指针(引用) 转换为 子类指针(引用):需要父类有虚函数
  A* aaa_ptr = new B();
  B* bb_ptr = dynamic_cast<B*>(aaa_ptr);
	return 0;
}

智能指针的转换

static_pointer_cast、dynamic_pointer_cast、const_pointer_cast、reinterpret_pointer_cast

虚函数表

动态多态是怎么实现的
如果一个类写了虚函数,那么编译器处理它时会生成虚函数表。类进行实例化时,对象中不仅有该类的数据成员,还会有一个虚函数表指针,同一个类实例化出来的对象使用的是同一个虚函数表。
当实例化子类对象时,子类从父类继承了虚函数,子类有一个自己的虚函数表,只不过该虚函数表里面的函数指针指向的函数和父类暂时一样,这样子类就能访问父类函数。
父类指针指向子类对象时:通过子类对象的虚函数表指针找到虚函数表,进而通过偏移在表中找到子类的函数。
虚函数表指针是用来指向该类虚函数表的,每个子类都有自己的虚函数表指针。
虚函数表就是带有函数指针的一个结构体,编译器为每个子类生成虚函数表的对象,因为具体实现的虚函数不同,使得虚函数表中的函数指针成员指向了不一样的实现函数,表现出来的结果就是调用结果不一样。
多继承导致的问题
多继承就多引入一个虚函数表指针,就多一个虚表,由于获取类成员地址时是根据对象地址加一个虚表指针的偏移量,而这里有多个虚表指针,如果使用的虚表指针不对,就不能正确取到类成员。解决方法是将对象指针强转为 类成员所在的父类 类型(上转是安全的),这样进行偏移就是正确的。
总而言之,多继承时,使用static_cast进行上转,dynamic_cast进行下转,而不是使用()进行强转。

奇异递归模板模式(CRTP)

静态多态(编译期多态)的实现有两种:函数重载、函数模板,CRTP是使用函数模板实现静态多态,比动态多态更快
CRTP实现了颠倒继承,也就是通过父类向子类添加功能。

#include<iostream>

template<class T>
class Base{
public:
  //准备让子类来具体实现的函数:实现静态多态,不使用virtual
  void fun(){
    static_cast<T*>(this)->sonfun();//这里是编译期就确定了,而不是运行时确认,因此上转时使用的static_cast
  }
  //这里应该写一个默认的sonfun实现。否则子类没实现这个函数时,编译不会报错(CRTP的缺点)

};

class Son1:public Base<Son1>{
public:
  //实现
  void sonfun(){
    std::cout<<"son1"<<std::endl;
  }
};

class Son2:public Base<Son2>{
public:
  //实现
  void sonfun(){
    std::cout<<"son2"<<std::endl;
  }
};

int main() {
  Base<Son1>* s1 = new Son1;//父类指针指向子类对象
  Base<Son2>* s2 = new Son2;
  s1->fun();
  s2->fun();
  return 0;
}

注意:父类需要写虚析构函数
虚析构函数:如果基类指针向派生类对象,则删除此指针时,我们希望调用该指针指向的派生类析构函数,而派生类的析构函数又调用基类的析构函数,这样整个派生类的对象完全被释放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值