免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

四种命名类型转换该用哪个:static/dynamic/const/reinterpret_cast

四种命名类型转换该用哪个:static/dynamic/const/reinterpret_cast 写 C 时你大概率见过(int)x这种 C 风格转换C-style cast显式类型转换。它看起来简单却是隐患最大的写法之一——编译器会在背后悄悄替你选一种「尽可能强」的转换你根本不知道它到底干了什么。C 为此专门设计了四种命名转换named cast把每种转换的意图和危险都摆在明面上。本文把四种转换讲透并告诉你为什么该彻底告别(T)x。1. 引子一个看不出危险的转换// 反例不要这么写下面这行悄悄去掉了 const编译器一声不吭constintc10;int*p(int*)(c);换成 C 的命名转换同样的效果你得显式写const_cast一眼就能看出「这里动了 const」。这就是命名转换存在的意义让意图可见、让危险可查。下面四种各管一摊。2. static_cast编译期转换绝不替你做运行时检查static_cast在编译期完成常用于数值类型之间、以及有继承关系的指针/引用向上转换。它不做运行时类型检查——向下转基类指针转派生类它也照转不误但转错了不会报错只是后续访问可能踩内存。#includecstdiointmain(){doubled3.9;intistatic_castint(d);// 浮点→整型截断编译期决定printf(static_castint(3.9) %d\n,i);intx65;charchstatic_castchar(x);// 窄化转换编译期完成无运行时开销printf(static_castchar(65) %c\n,ch);}static_castint(3.9) 3 static_castchar(65) A注意static_cast不能去掉const那是const_cast的活也不能在毫无关系的指针类型间硬转那是reinterpret_cast。它的「安全」在于编译期就能挡住明显非法的转换。官方文档static_cast — cppreference罗列了 static_cast 能做和不能做的全部情形。3. dynamic_cast运行期检查需要多态类型有 RTTI 开销dynamic_cast是唯一在运行期检查类型转换是否合法的命名转换。它要求源类型至少是多态的有虚函数会借助 RTTIRun-Time Type Information运行时类型信息查询真实类型指针形式失败返回nullptr不抛异常引用形式失败抛出std::bad_cast。#includecstdiostructAnimal{virtual~Animal()default;};// 必须有虚函数dynamic_cast 才可用structDog:Animal{voidbark(){printf(woof\n);}};structCat:Animal{};intmain(){Dog dog;Animal*adog;// 指向栈上的 Dog无需 new/deleteif(Dog*ddynamic_castDog*(a)){// 运行时确认a 实际是 Dogprintf(真的是 Dog);d-bark();}if(dynamic_castCat*(a)){// 实际不是 Cat返回 nullptrprintf(是 Cat\n);}else{printf(不是 Cat指针形式返回 nullptr\n);}}真的是 Dogwoof 不是 Cat指针形式返回 nullptr引用形式的失败表现为抛异常#includecstdio#includetypeinfostructAnimal{virtual~Animal()default;};structDog:Animal{};structCat:Animal{};intmain(){Dog dog;Animaladog;try{Catcdynamic_castCat(a);// 引用形式失败抛 std::bad_cast(void)c;}catch(conststd::bad_cast){printf(引用形式失败抛出 std::bad_cast\n);}}引用形式失败抛出 std::bad_cast代价每次dynamic_cast都要查 RTTI有运行时开销而且它依赖虚函数表基类没有虚函数就用不了。所以能用static_cast确定方向的向上转换就别用dynamic_cast浪费开销。官方文档dynamic_cast — cppreference指针/引用两种失败语义以及「必须有虚函数」的前提。4. const_cast唯一能去 const 的用错就是未定义行为const_cast是唯一能增删const/volatile的命名转换。它安全的前提是被指向的对象本身就不是 const。如果对象本身就是const你去掉 const 再去写就是未定义行为Undefined BehaviorUB。#includecstdio// p 指向的对象本身非 const只是「通过 const 指针看到它」—— 这种情况去 const 是安全的voidedit(int*p){*p100;}intmain(){intx1;// 本身非 constconstint*cpx;// 只是换了个 const 的视角int*wconst_castint*(cp);// 安全x 本来就能改*w100;printf(x %d对象本身可写const_cast 只是撕掉了指针的 const 标签\n,x);}x 100对象本身可写const_cast 只是撕掉了指针的 const 标签反面教材反例不要这么写对象本身const却用const_cast去改它结果是 UB——可能改成功、可能原值纹丝不动、甚至可能崩溃行为完全不可预测// 反例不要这么写y 本身是 const去掉 const 再写是未定义行为constinty5;int*pyconst_castint*(y);*py99;// ✗ UB修改真正的 const 对象后果不可预测官方文档const_cast — cppreference明确警告「修改原本 const 的对象是 UB」。5. reinterpret_cast位重解释最危险的一种reinterpret_cast做的是最底层的位级重新解释bit reinterpretation——把一段内存的位模式原封不动当成另一种类型来读。它几乎不做任何检查是四种里最危险的用错轻则得到无意义的值重则触发严格别名规则strict aliasing违规导致 UB。#includecstdiointmain(){intn7;// 把「对象指针」重新解释成「另一种指针类型」—— 不做任何数值转换只改类型标签void*vpn;int*qreinterpret_castint*(vp);// 原路还原到原来的类型行为良定义*q7;printf(reinterpret_cast 往返后 n %d\n,n);}reinterpret_cast 往返后 n 7真正危险的是「把 A 类型的位当成 B 类型访问」比如把float的 32 位直接当std::uint32_t读——这违反了严格别名规则是 UB实际代码请用 C20 的std::bit_cast或std::memcpy// 反例不要这么写float 的位被当成 uint32_t 访问违反严格别名规则UBfloatf1.0f;std::uint32_tbitsreinterpret_caststd::uint32_t(f);// ✗ 危险官方文档reinterpret_cast — cppreference允许/禁止的情形清单以及它和严格别名规则的关系。6. 为什么必须避开 C 风格转换(T)xC 风格转换会在编译期按固定顺序依次尝试四种转换谁能胜任就用谁C 风格转换 (T)expr 的解析顺序 ① const_cast 能胜任吗去 const / volatile → 用 const_cast ② 否则 static_cast 能胜任吗 → 用 static_cast ③ 否则用 static_cast reinterpret_cast 的组合 → 用 reinterpret_cast 你写代码时根本不知道最终走了哪一步。问题就在这「不知道」一个(int*)const_obj悄悄走了const_cast剥掉 const一个跨继承的(Derived*)base_ptr可能走了reinterpret_cast级别的危险强转——而这些在命名转换里本该是分开、且需要你显式承认的动作。看对比#includecstdiointmain(){constintc10;// static_cast 不能去掉 const —— 下面这行若取消注释会编译失败提前拦住你// int* p1 static_castint*(c);// C 风格转换却能悄悄去掉 const内部先试了 const_cast危险被藏起来了int*p2(int*)(c);// 反例不要这么写const 被无声剥离(void)p2;printf(C 风格转换悄悄去掉了 const反例应改用 const_cast 明示意图\n);}C 风格转换悄悄去掉了 const反例应改用 const_cast 明示意图结论永远用四种命名转换之一让「我要做什么转换、接不接受它的风险」白纸黑字写在代码里。7. 四者对比检查时机、开销、安全性转换检查时机运行时开销安全性static_cast编译期无中不做运行期校验向下转错类型不报错dynamic_cast运行期查 RTTI有虚表查询高失败返回nullptr/ 抛bad_castconst_cast编译期无低对象本身 const 时去 const写即 UBreinterpret_cast编译期无极低位重解释极易触发别名/对齐 UB哪种写法更优dynamic_cast vs static_cast 向下转向下转换基类指针 → 派生类指针时两者都能写但语义天差地别写法是否检查转错时适用static_castDerived*(base)否返回野指针后续访问 UB你确定类型一定对如刚new Derived上来dynamic_castDerived*(base)是返回nullptr可判空类型不确定需要运行期安全兜底结论类型确定用static_cast零开销类型不确定用dynamic_cast花 RTTI 换安全。绝不用 C 风格转换糊弄过去。8. 完整示例四种转换各归各位把四种转换的正确用法放进一个整体可编译的程序#includecstdiostructShape{virtual~Shape()default;};structCircle:Shape{intr5;};intmain(){doublepi3.14159;intipstatic_castint(pi);// ① 数值截断编译期完成printf(static_castint(pi) %d\n,ip);Circle c;Shape*sc;if(Circle*cpdynamic_castCircle*(s)){// ② 运行期检查安全兜底printf(dynamic_cast 命中半径 %d\n,cp-r);}intm7;constint*cmm;// 指向非 const 对象只是加了 const 视角int*mpconst_castint*(cm);// ③ 去掉 const 标签对象本身可写*mp9;printf(const_cast 后 m %d\n,m);void*vpip;int*ippreinterpret_castint*(vp);// ④ 指针重新解释原路还原printf(reinterpret_cast 往返 %d\n,*ipp);}static_castint(pi) 3 dynamic_cast 命中半径 5 const_cast 后 m 9 reinterpret_cast 往返 39. 延伸阅读static_cast / dynamic_cast / const_cast / reinterpret_cast — cppreference四种转换的权威语法与约束建议作为速查。C Core Guidelines · ES.48明确「避免强制转换尤其避免 C 风格转换」。Strict aliasing — cppreference理解 reinterpret_cast 为何危险的根因。10. 一句话总结四种命名转换各管一摊static_cast编译期转换不查运行期、dynamic_cast用 RTTI 做运行期检查失败返回 nullptr/抛 bad_cast、const_cast是唯一能去 const 但对象本身 const 时写即 UB、reinterpret_cast做位重解释最危险。永远用命名转换代替 C 风格(T)x——后者会按「const→static→reinterpret」顺序偷偷选一种把危险藏得无影无踪。
返回列表