跳到主要内容
C++ 异常处理机制与类型转换详解 | 极客日志
C++
C++ 异常处理机制与类型转换详解 综述由AI生成 本文详细阐述了 C++ 异常处理机制与类型转换技术。内容涵盖异常概念、抛出捕获流程、栈展开规则及自定义异常继承体系设计,强调了异常安全原则与 noexcept 规范的应用。同时深入解析了 C 与 C++ 类型转换的差异,重点讲解了 static_cast、dynamic_cast、const_cast 和 reinterpret_cast 四种显式转换运算符的安全性与适用场景。此外,还介绍了 RTTI 运行时类型识别机制及其优缺点,为编写健壮、安全的 C++ 代码提供了实战指导。
宁静 发布于 2026/3/24 更新于 2026/4/25 1 浏览C++ 异常处理机制与类型转换详解
1. 异常的概念及使用
1.1 异常的概念
异常(Exception)是程序运行时发生的、打断正常指令执行流程的意外错误或异常事件,是程序与开发者之间用于传递运行故障的标准化机制。
核心界定:什么是异常?
异常特指程序运行阶段 出现的错误,并非代码语法错误:
编译错误 :语法写错、少分号、类型不匹配(编译器直接报错,无法运行);
异常 :程序已成功编译运行,执行中触发的不可预知问题。
C++ 中常见的异常场景
整数除零运算;
访问空指针、数组越界;
动态内存分配失败(new 申请内存不足);
打开不存在的文件、网络连接断开;
自定义业务逻辑错误(如参数非法、数据格式错误)。
本质:错误处理的现代化方案
在没有异常机制时,C/C++ 传统的错误处理依赖返回值/错误码 (如函数返回 -1、0 表示失败),这种方式存在致命缺陷:
错误处理代码与正常逻辑耦合,代码臃肿混乱;
开发者极易忽略错误检查,导致故障扩散;
多层函数调用时,错误码需要层层传递,维护成本极高。
C++ 异常机制的核心思想 :
将错误的检测 与错误的处理 彻底分离:
函数内部检测到错误时,抛出 (throw) 异常信息,终止当前代码执行;
程序跳转到指定的捕获 (catch) 模块处理错误,不干扰正常业务逻辑。
异常机制的三大核心关键字
C++ 用三个关键字实现完整的异常流程,是异常概念的核心载体:
throw :抛出异常,主动上报运行时错误;
try :包裹可能触发异常的代码块,标记需要监控错误的区域;
catch :捕获并处理对应类型的异常,是错误的兜底方案。
一句话总结:异常是 C++ 用于处理运行时错误的专用机制,通过抛出 - 捕获模型分离错误检测与处理逻辑,让程序在遭遇意外时不崩溃、可恢复,是编写健壮、安全的工业级代码的基础能力。
1.2 异常的抛出和捕获
程序出现问题时,我们通过抛出 (throw) 一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个 catch 的处理代码来处理该异常。
被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出异常部分告知异常处理部分到底发生了什么错误。
当 throw 执行时,throw 后面的语句将不再被执行。程序的执行从 throw 位置跳到与之匹配的 catch 模块,catch 可能是同一函数中的一个局部的 catch,也可能是调用链中另一个函数中的 catch,控制权从 throw 位置转移到了 catch 位置。这里还有两个重要的含义:
沿着调用链的函数可能提早退出。
一旦程序开始执行异常处理程序,沿着调用链创建的对象都将销毁。
抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝的对象会在 catch 子句后销毁。(这里的处理类似于函数的传值返回)。
1.3 栈展开
抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的 catch 子句,首先检查 throw 本身是否在 try 块内部,如果在则查找匹配的 catch 语句,如果有匹配的,则跳到 catch 的地方进行处理。
如果当前函数中没有 try/catch 子句,或者有 try/catch 子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的 catch 过程被称为栈展开。
如果到达 main 函数,依旧没有找到匹配的 catch 子句,程序会调用标准库的 terminate 函数终止程序。
如果找到匹配的 catch 子句处理后,catch 子句代码会继续执行。
#include <iostream>
using namespace std;
double Divide (int a, int b) {
try {
if (b == 0 ) {
string s ("Divide by zero condition!" ) ;
throw s;
} else {
return ((double )a / (double )b);
}
} catch (int errid) {
cout << errid << endl;
}
return 0 ;
}
void Func () {
int len, time;
cin >> len >> time;
try {
cout << Divide (len, time) << endl;
} catch (const char * errmsg) {
cout << errmsg << endl;
}
cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
}
int main () {
while (1 ) {
try {
Func ();
} catch (const string& errmsg) {
cout << errmsg << endl;
}
}
return 0 ;
}
1.4 查找匹配的处理代码
一般情况下抛出对象和 catch 是类型完全匹配的,如果有多个类型匹配的,就选择离它位置更近的那个。
但是也有一些例外,允许从非常量向常量的类型转换,也就是权限缩小;允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许从派生类向基类类型的转换,这个点非常实用,实际中继承体系基本都是用这个方式设计的。
如果到 main 函数,异常仍旧没有被匹配就会终止程序,不是发生严重错误的情况下,我们是不期望程序终止的,所以一般 main 函数中最后都会使用 catch(...),它可以捕获任意类型的异常,但是不知道异常错误是什么。
在实际大型项目中,通常设计自定义异常继承体系。下面模拟设计一个服务的几个模块,每个模块的继承都是 Exception 的派生类,每个模块可以添加自己的数据,最后捕获时,我们捕获基类就可以。
#define _CRT_SECURE_NO_WARNINGS 1
#include <thread>
#include <iostream>
#include <string>
using namespace std;
class Exception {
public :
Exception (const string& errmsg, int id):_errmsg(errmsg),_id(id){}
virtual string what () const { return _errmsg; }
int getid () const { return _id; }
protected :
string _errmsg;
int _id;
};
class SqlException : public Exception {
public :
SqlException (const string& errmsg, int id, const string& sql)
: Exception (errmsg, id),_sql(sql) {}
virtual string what () const {
string str = "SqlException:" ;
str += _errmsg;
str += "->" ;
str += _sql;
return str;
}
private :
const string _sql;
};
class CacheException : public Exception {
public :
CacheException (const string& errmsg, int id) : Exception (errmsg, id) {}
virtual string what () const {
string str = "CacheException:" ;
str += _errmsg;
return str;
}
};
class HttpException : public Exception {
public :
HttpException (const string& errmsg, int id, const string& type)
: Exception (errmsg, id),_type(type) {}
virtual string what () const {
string str = "HttpException:" ;
str += _type;
str += ":" ;
str += _errmsg;
return str;
}
private :
const string _type;
};
void SQLMgr () {
if (rand () % 7 == 0 ) {
throw SqlException ("权限不足" , 100 , "select * from name = '张三'" );
} else {
cout << "SQLMgr 调用成功" << endl;
}
}
void CacheMgr () {
if (rand () % 5 == 0 ) {
throw CacheException ("权限不足" , 100 );
} else if (rand () % 6 == 0 ) {
throw CacheException ("数据不存在" , 101 );
} else {
cout << "CacheMgr 调用成功" << endl;
}
SQLMgr ();
}
void HttpServer () {
if (rand () % 3 == 0 ) {
throw HttpException ("请求资源不存在" , 100 , "get" );
} else if (rand () % 4 == 0 ) {
throw HttpException ("权限不足" , 101 , "post" );
} else {
cout << "HttpServer 调用成功" << endl;
}
CacheMgr ();
}
int main () {
srand (time (0 ));
while (1 )
{
this_thread::sleep_for (chrono::seconds (1 ));
try {
HttpServer ();
} catch (const Exception& e)
{
cout << e.what () << endl;
}
catch (...) {
cout << "Unkown Exception" << endl;
}
}
return 0 ;
}
模拟后端服务三层架构:HttpServer(网络层) → CacheMgr(缓存层) → SQLMgr(数据库层);
自定义异常继承体系:基类 Exception, 派生数据库、缓存、HTTP 三种专用异常;
随机触发异常:模拟业务运行时的随机错误;
多态捕获异常:在主函数仅捕获基类异常引用,即可统一处理所有派生类异常;
死循环 + 每秒执行:持续模拟服务运行。
核心:自定义异常继承体系
1️⃣这是大型项目异常设计的标准规范:基类抽象异常 + 派生类细分业务异常。
(1) 异常基类 Exception
✅ 设计要点:what() 定义为 virtual 虚函数:为了多态调用(捕获基类时,能执行派生类重写的逻辑);成员用 protected:允许派生类直接访问,无需写 get/set;作为所有业务异常的基类,统一捕获入口。
(2) 派生类:细分业务异常
三个派生类分别对应数据库、缓存、HTTP 模块,重写 what() 定制专属错误信息,并扩展独有成员。
2️⃣ 业务模块函数:分层调用 + 抛异常
代码模拟了三层服务调用链:main() → HttpServer() → CacheMgr() → SQLMgr()。函数内部用 rand() 随机触发异常,throw 抛出派生类异常对象。
✅ 异常机制关键行为 :一旦执行 throw,当前函数立即终止,程序会栈展开,直接跳转到最近的 catch 块,中间所有未执行完的代码都不会运行。
1.5 异常重新抛出 有时 catch 到一个异常对象后,需要对错误进行分类,其中的某种异常错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链处理。捕获异常后需要重新抛出,直接 throw 就可以把捕获的对象直接抛出。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
class Exception {
public :
Exception (const string& errmsg, int id):_errmsg(errmsg),_id(id){}
virtual string what () const { return _errmsg; }
int getid () const { return _id; }
protected :
string _errmsg;
int _id;
};
class HttpException : public Exception {
public :
HttpException (const string& errmsg, int id, const string& type)
: Exception (errmsg, id),_type(type) {}
virtual string what () const override {
string str = "HttpException:" ;
str += _type;
str += ":" ;
str += _errmsg;
return str;
}
private :
const string _type;
};
void _SeedMsg(const string& s) {
if (rand () % 2 == 0 ) {
throw HttpException ("网络不稳定,发送失败" , 102 , "put" );
} else if (rand () % 7 == 0 ) {
throw HttpException ("你已经不是对象的好友,发送失败" , 103 , "put" );
} else {
cout << "发送成功" << endl;
}
}
void SendMsg (const string& s) {
for (size_t i = 0 ; i < 4 ; i++) {
try {
_SeedMsg(s);
break ;
} catch (const Exception& e) {
if (e.getid () == 102 ) {
if (i == 3 ) throw ;
cout << "开始第" << i + 1 << "重试" << endl;
} else {
throw ;
}
}
}
}
int main () {
srand (time (0 ));
string str;
while (cin >> str) {
try {
SendMsg (str);
} catch (const Exception& e) {
cout << e.what () << endl << endl;
} catch (...) {
cout << "Unkown Exception" << endl;
}
}
return 0 ;
}
1.6 异常安全问题
异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。中间我们需要捕获异常,释放资源后再重新抛出,当然后续智能指针会介绍的 RAII 方式解决这种问题是更好的。
其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放 10 个资源,释放到第 5 个时抛出异常,则也需要捕获处理,否则后面的 5 个资源就没释放,也资源泄漏了。《Effective C++》第 8 个条款也专门讲了这个问题,别让异常逃离析构函数。
#include <iostream>
using namespace std;
double Divide (int a, int b) {
if (b == 0 ) {
throw "Division by zero condition!" ;
}
return (double )a / (double )b;
}
void Func () {
int * array = new int [10 ];
try {
int len, time;
cin >> len >> time;
cout << Divide (len, time) << endl;
} catch (...) {
cout << "delete []" << array << endl;
delete [] array;
throw ;
}
cout << "delete []" << array << endl;
delete [] array;
}
int main () {
try {
Func ();
} catch (const char * errmsg) {
cout << errmsg << endl;
} catch (const exception& e) {
cout << e.what () << endl;
} catch (...) {
cout << "Unkown Exception" << endl;
}
return 0 ;
}
1.7 异常规范
对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异常有助于简化调用函数的代码。
C++98 中函数参数列表的后面接 throw(),表示函数不抛异常,函数参数列表的后面接 throw(类型 1,类型 2…) 表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。
C++98 的方式这种方式过于复杂,实践中并不好用,C++11 中进行了简化,函数参数列表后面加 noexcept 表示不会抛出异常,啥都不加表示可能会抛出异常。
编译器并不会在编译时检查 noexcept,也就是说如果一个函数用 noexcept 修饰了,但是同时又包含了 throw 语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了 noexcept 的函数抛出了异常,程序会调用 terminate 终止程序。
noexcept(expression) 还可以作为一个运算符去检测一个表达式是否会抛出异常,可能会则返回 false,不会就返回 true。
#include <iostream>
using namespace std;
class MyContainer {
private :
int * _data; size_t _size;
public :
MyContainer (size_t size):_size(size) { _data = new int [size]();
~MyContainer () { delete [] _data; }
size_t size () const noexcept { return _size; }
int * begin () noexcept { return _data; }
const int * begin () const noexcept { return _data; }
};
double Divide (int a, int b) {
if (b == 0 ) {
throw "Division by zero condition!" ;
}
return (double )a / (double )b;
}
int main () {
try {
int len, time;
cin >> len >> time;
cout << Divide (len, time) << endl;
} catch (const char * errmsg) {
cout << "捕获异常:" << errmsg << endl;
} catch (...) {
cout << "Unkown Exception" << endl;
}
int i = 0 ;
cout << "noexcept(Divide(1,2))= " << noexcept (Divide (1 ,2 )) << "(1=不抛,0=抛)" << endl;
cout << "noexcept(Divide(1,0))=" << noexcept (Divide (1 ,0 )) << "(1=不抛,0=抛)" << endl;
cout << "noexcept(++i)=" << noexcept (++i) << "(1=不抛,0=抛)" << endl;
MyContainer container (5 ) ;
cout << "容器大小 size = " << container.size () << endl;
return 0 ;
}
2. 标准库的异常 这张图是 C++ 标准库中 std::exception 类的官方文档,它是整个 C++ 异常体系的根基类。
头文件 :<exception>
身份 :std::exception 是所有 C++ 标准库异常的公共基类。
核心价值 :标准库中所有组件抛出的异常(如内存分配失败 std::bad_alloc、越界访问 std::out_of_range 等)都继承自它。因此,只要捕获 const std::exception&,就能统一捕获所有标准库异常,实现错误的集中处理。
2️⃣ C++98 版本的类声明解析
⚠️ 注意 :throw() 是 C++98 的动态异常规范,用来声明函数可能抛出的异常类型;throw() 空括号表示不会抛出任何异常。C++11 之后,这种写法被 noexcept 关键字替代,但功能完全等价。
多态捕获 :因为 what() 是虚函数,当你捕获基类 std::exception 引用时,调用 e.what() 会自动绑定到实际派生类的实现,从而拿到具体的错误描述。
自定义异常的基类 :实际项目中,我们也会继承 std::exception 来实现自定义异常类,这样就能和标准库异常体系兼容,被统一捕获。
4️⃣ C++11 之后的演进
C++11 引入 noexcept 后,std::exception 的声明被更新为:
把 throw() 替换为 noexcept,语义完全一致,但语法更简洁,是现代 C++ 的推荐写法。
核心接口 what() 保持不变,保证了向后兼容。
C++ 标准库也定义了一套自己的一套异常继承体系库,基类是 exception,所以我们日常写程序,需要在主函数捕获 exception 即可,要获取异常信息,调用 what 函数,what 是一个虚函数,派生类可以重写。
3. C 语言中的类型转换
在 C 语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时等场景,就需要发生类型转化,C 语言中总共有两种形式的类型转换:隐式类型转换 和显式强制类型转换 。
隐式类型转化 :编译器在编译阶段自动进行,能转就转,不能转就编译失败。
显式强制类型转化 :需要用户自己去显示在变量前用括号指定要转换的类型。
并不是任意类型之前都支持转换,两个类型支持转换需要有一定关联性,也就是说转换后要有一定的意义,两个毫无关联的类型是不支持转换的。
#include <iostream>
using namespace std;
int main () {
int i = 1 ;
double d = i;
printf ("%d, %.2f\n" , i, d);
int * p = &i;
int address = (int )p;
printf ("%p, %d\n" , p, address);
int * ptr = (int *)malloc (8 );
return 0 ;
}
4. C++ 中的类型转换
C++ 兼容 C,所以 C 支持的隐式类型转换和显式强制类型转换 C++ 都支持。
C++ 还支持内置类型到自定义类型之间的转换,内置类型转成自定义类型需要构造函数的支持,自定义类型转成内置类型,需要一个 operator 类型 () 的函数去支持。
C++ 还支持自定义类型到自定义类型之间的转换,需要对应类型的构造函数支持即可,比如 A 类型对象想转成 B 类型,则支持一个形参为 A 类型的 B 构造函数即可支持。
#include <iostream>
using namespace std;
class A {
public :
A (int a):_a1(a),_a2(a) {}
A (int a1,int a2):_a1(a1),_a2(a2) {}
operator int () const { return _a1 + _a2; }
private :
int _a1 = 1 ;
int _a2 = 1 ;
};
class B {
public :
B (int b):_b1(b) {}
B (const A& aa):_b1(aa) {}
private :
int _b1 = 1 ;
};
int main () {
string s1 = "1111111" ;
A aa1 = 1 ;
A aa2 = (A)1 ;
A aa3 = {2 ,2 };
const A& aa4 = {2 ,2 };
int z = aa1. operator int ();
int x = aa1;
int y = (int )aa2;
cout << x << endl;
cout << y << endl;
cout << z << endl;
std::shared_ptr<int > foo;
std::shared_ptr<int > bar (new int (34 )) ;
if (foo) std::cout << "foo points to " << *foo << '\n' ;
else std::cout << "foo is null\n" ;
if (bar) std::cout << "bar points to " << *bar << '\n' ;
else std::cout << "bar is null\n" ;
B bb1 = aa1;
B bb2 (2 ) ;
bb2 = aa1;
const B& ref1 = aa1;
return 0 ;
}
#include <iostream>
#include <assert.h>
using namespace std;
namespace lcz {
template <class T >
struct ListNode {
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode (const T& data = T ()):_next(nullptr ),_prev(nullptr ),_data(data) {}
};
template <class T ,class Ref ,class Ptr >
struct ListIterator {
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator (Node* node):_node(node) {}
ListIterator (const ListIterator<T, T&, T*>& it):_node(it._node) {}
Self& operator ++() { _node = _node->_next; return *this ; }
Self& operator --() { _node = _node->_prev; return *this ; }
Self operator ++(int ) { Self tmp (*this ) ; _node = _node->_next; return tmp; }
Self& operator --(int ) { Self tmp (*this ) ; _node = _node->_prev; return tmp; }
Ref operator *() { return _node->_data; }
Ptr operator ->() { return &_node->_data; }
bool operator !=(const Self& it) { return _node != it._node; }
bool operator ==(const Self& it) { return _node == it._node; }
};
template <class T >
class list {
typedef ListNode<T> Node;
public :
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T,const T&,const T*> const_iterator;
iterator begin () { return iterator (_head->_next); }
const_iterator begin () const { return const_iterator (_head->_next); }
iterator end () { return iterator (_head); }
const_iterator end () const { return const_iterator (_head); }
void empty_init () { _head = new Node (); _head->_next = _head; _head->_prev = _head; }
list () { empty_init (); }
list (initializer_list<T> il) { empty_init (); for (const auto & e : il) { push_back (e); } }
void push_back (const T& x) { insert (end (), x); }
iterator insert (iterator pos, const T& x) {
Node* cur = pos._node;
Node* newnode = new Node (x);
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator (newnode);
}
private :
Node* _head;
};
}
int main () {
lcz::list<int > lt = {1 ,2 ,3 ,4 };
lcz::list<int >::const_iterator cit = lt.begin ();
while (cit != lt.end ()) {
cout << *cit << " " ;
++cit;
}
cout << endl;
return 0 ;
}
5. C++ 显示强制类型转换
5.1 类型安全
类型安全 是指编程语言在编译和运行时提供保护机制,避免非法的类型转换和操作,导致出现一个内存访问错误等,从而减少程序运行时的错误。
C 语言不是类型安全的语言,C 语言允许隐式类型转换,一些特殊情况下就会导致越界访问的内存错误,其次不合理的使用强制类型转换也会导致问题,比如一个 int* 的指针强转成 double* 访问就会出现越界。
C++ 兼容 C 语言,支持隐式类型转换和强制类型转换,C++ 也不是类型安全的语言,C++ 提出 4 个显示的命名强制类型转换 static_cast/reinterpret_cast/const_cast/dynamic_cast 就是为了让类型转换相对而言更安全。
#include <iostream>
using namespace std;
void insert (size_t pos, char ch) {
int end = 10 ;
while (end >= pos) {
cout << end << endl;
--end;
}
}
int main () {
insert (5 ,'x' );
int x = 100 ;
double * p1 = (double *)&x;
cout << *p1 << endl;
const int y = 0 ;
int * p2 = (int *)&y;
(*p2) = 1 ;
cout << *p2 << endl;
cout << y << endl;
return 0 ;
}
5.2 C++ 中 4 个显示强制类型转换运算符
static_cast 用于两个类型意义相近的转换,这个转换是具有明确定义的,只要底层不包含 const,都可以使用 static_cast。
reinterpret_cast 用于两个类型意义不相近的转换,reinterpret 是重新解释的意思,通常为运算对象的位模式提供较低层次上的重新解释,也就是说转换后对原有内存的访问解释已经完全改变了,非常的大胆。所以我们要谨慎使用,清楚知道这样转换是没有内存访问安全问题的。
const_cast 用于 const 类型到非 const 类型的转换,去掉了 const 属性,也是一样的我们要谨慎使用,否则可能会出现意想不到的结果。
dynamic_cast 用于将基类的指针或者引用安全的转换成派生类的指针或者引用。如果基类的指针或者引用时指向派生类对象的,则转换回派生类指针或者引用时可以成功的,如果基类的指针指向基类对象,则转换失败返回 nullptr,如果基类引用指向基类对象,则转换失败,抛出 bad_cast 异常。
其次 dynamic_cast 要求基类必须是多态类型,也就是基类中必须有虚函数。因为 dynamic_cast 是运行时通过虚表中存储的 type_info 判断基类指针指向的是基类对象还是派生类对象。
#include <iostream>
using namespace std;
int main () {
double d = 12.34 ;
int a = static_cast <int >(d);
cout << a << endl;
int && ref = static_cast <int &&>(a);
int * p1 = reinterpret_cast <int *>(a);
volatile const int b = 0 ;
int * p2 = const_cast <int *>(&b);
*p2 = 1 ;
cout << b << endl;
cout << *p2 << endl;
return 0 ;
}
#include <iostream>
using namespace std;
class A {
public :
virtual void f () {}
int _a = 1 ;
};
class B : public A {
public :
int _b = 2 ;
};
void fun1 (A* pa) {
B* pb1 = (B*)pa;
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++; pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
}
void fun2 (A* pa) {
B* pb1 = dynamic_cast <B*>(pa);
if (pb1) {
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++; pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
} else {
cout << "转换失败" << endl;
}
}
void fun3 (A& pa) {
try {
B& pb1 = dynamic_cast <B&>(pa);
cout << "转换成功" << endl;
} catch (const exception& e) {
cout << e.what () << endl;
}
}
int main () {
A a; B b;
fun2 (&a);
fun2 (&b);
fun3 (a);
fun3 (b);
return 0 ;
}
6. RTTI(运行时类型信息)
RTTI 的英文全称是 "Runtime Type Identification",中文称为 运行时类型识别 ,它指的是程序在运行的时候才确定需要用到的对象是什么类型的。用于在运行时(而不是编译时)获取有关对象的信息。
RTTI 主要由两个运算符实现,typeid 和 dynamic_cast;typeid 主要用于返回表达式的类型,dynamic_cast 前面已经介绍了,主要用于将基类的指针或者引用安全的转换成派生类的指针或者引用。
typeid(e) 中 e 可以是任意表达式或类型的名字,typeid(e) 的返回值是 typeinfo 或 typeinfo 派生类对象的引用,typeinfo 可以只支持比较等于和不等于,name 成员函数可以返回 C 风格字符串表示对象类型名字,typeinfo 的精确定义随着编译器的不同而略有差异,也就意味着同一个 e 表达式,不同编译器下,typeid(e).name() 返回的名字可能是不一样的。
typeinfo 的文档
typeid(e) 时,当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid 返回的是运算对象的静态类型,当运算对象是定义了至少一个虚函数的类的左值时,typeid 的返回结果直到运行时才会求得。
优点
运行时动态识别类型,灵活处理多态对象;
dynamic_cast 安全转型,避免野指针、非法访问。
缺点
运行时开销:需要存储额外的类型信息,降低效率;
破坏封装性:优先使用虚函数多态,而非 RTTI 判断类型;
现代 C++ 项目中,尽量少用 RTTI。
#include <iostream>
#include <string>
#include <vector>
#include <list>
using namespace std;
int main () {
int a[10 ];
int * ptr = nullptr ;
cout << typeid (10 ).name () << endl;
cout << typeid (a).name () << endl;
cout << typeid (ptr).name () << endl;
cout << typeid (string).name () << endl;
cout << typeid (string::iterator).name () << endl;
cout << typeid (vector<int >).name () << endl;
cout << typeid (vector<int >::iterator).name () << endl;
return 0 ;
}
#include <iostream>
using namespace std;
class A {
public :
virtual void func () {}
protected :
int _a1 = 1 ;
};
class B : public A {
protected :
int _b1 = 2 ;
};
int main () {
try {
B* pb = new B;
A* pa = (A*)pb;
if (typeid (*pb) == typeid (B)) {
cout << "typeid(*pb) == typeid(B)" << endl;
}
if (typeid (*pa) == typeid (B)) {
cout << "typeid(*pa) == typeid(B)" << endl;
}
if (typeid (pa) == typeid (pb)) {
cout << "typeid(pa) == typeid(B)" << endl;
}
} catch (const std::exception& e) {
cout << e.what () << endl;
}
return 0 ;
}
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online