提问人:Graeme Perrow 提问时间:8/26/2008 最后编辑:R. Martinho FernandesGraeme Perrow 更新时间:7/25/2023 访问量:892839
常规演员 vs. static_cast vs. dynamic_cast [重复]
Regular cast vs. static_cast vs. dynamic_cast [duplicate]
问:
我编写 C 和 C++ 代码已经将近二十年了,但这些语言的一个方面我从未真正理解过。我显然使用了常规演员表,即
MyClass *m = (MyClass *)ptr;
到处都是,但似乎还有另外两种类型的演员,我不知道有什么区别。以下代码行之间有什么区别?
MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);
答:
dynamic_cast
具有运行时类型检查,仅适用于引用和指针,而不提供运行时类型检查。有关完整信息,请参阅 MSDN 文章 static_cast Operator(运算符)。static_cast
您应该查看文章 C++ 编程/类型转换。
它包含了对所有不同铸件类型的良好描述。以下内容摘自上述链接:
const_cast
const_cast(expression) const_cast<>() 用于添加/删除 const(ness) (或 volatile-ness)。
static_cast
static_cast(expression) static_cast<>() 用于在 整数类型。例如,char->long、int->short 等。
静态强制转换还用于强制转换指向相关类型的指针,用于 示例将 void* 转换为适当的类型。
dynamic_cast
动态强制转换用于在运行时转换指针和引用, 通常用于向上或向下转换指针或引用 继承链(继承层次结构)。
dynamic_cast(表达式)
目标类型必须是指针或引用类型,并且 表达式的计算结果必须为指针或引用。动态演员作品 仅当表达式所引用的对象类型为 与目标类型兼容,并且基类至少有一个 虚拟成员函数。如果不是,以及要强制转换的表达式类型 是指针,如果对引用进行动态强制转换,则返回 NULL 失败,则引发bad_cast异常。当它不失败时,动态 cast 返回指向对象的目标类型的指针或引用 表达所指的。
reinterpret_cast
重新解释强制转换只是将一种类型按位强制转换为另一种类型。任何指针 或者整数类型可以转换为任何其他带有重新解释的强制转换, 容易被误用。例如,使用重新解释铸件一 可能会不安全地将整数指针强制转换为字符串指针。
C 型演员将 const_cast、static_cast 和 reinterpret_cast 混为一谈。
我希望 C++ 没有 C 风格的转换。C++ 强制转换适当地突出(它们应该如此;强制转换通常表示做了坏事),并正确区分强制转换执行的不同类型的转换。它们还允许编写外观相似的函数,例如 boost::lexical_cast,从一致性的角度来看,这非常好。
仅供参考,我相信引用 Bjarne Stroustrup 的话说,应避免使用 C 型演员,如果可能的话,您应该使用 static_cast 或 dynamic_cast。
Barne Stroustrup 的 C++ 风格常见问题解答
随心所欲地接受这个建议。我远不是C++大师。
评论
避免使用 C 样式转换。
C 样式强制转换是常量强制转换和重新解释强制转换的混合体,很难在代码中找到和替换。C++ 应用程序程序员应避免 C 样式强制转换。
static_cast
static_cast
用于基本上想要撤消隐式转换的情况,但有一些限制和添加。 不执行运行时检查。如果您知道您引用了特定类型的对象,则应使用此方法,因此不需要进行检查。例:static_cast
void func(void *data) {
// Conversion from MyClass* -> void* is implicit
MyClass *c = static_cast<MyClass*>(data);
...
}
int main() {
MyClass c;
start_thread(&func, &c) // func(&c) will be called
.join();
}
在此示例中,您知道您传递了一个对象,因此不需要运行时检查来确保这一点。MyClass
dynamic_cast
dynamic_cast
当您不知道对象的动态类型是什么时,这很有用。如果引用的对象不包含作为基类强制转换为的类型,则返回 null 指针(强制转换为引用时,在这种情况下会引发异常)。bad_cast
if (JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
...
} else if (ExprStm *e = dynamic_cast<ExprStm*>(&stm)) {
...
}
如果参数类型不是多态的,则不能用于向下转换(强制转换为派生类)。例如,以下代码无效,因为它不包含任何虚拟函数:dynamic_cast
Base
struct Base { };
struct Derived : Base { };
int main() {
Derived d; Base *b = &d;
dynamic_cast<Derived*>(b); // Invalid
}
“up-cast”(强制转换为基类)对 和 始终有效,并且没有任何强制转换,因为“up-cast”是一种隐式转换(假设基类是可访问的,即它是一个继承)。static_cast
dynamic_cast
public
常规演员表
这些铸件也称为 C 型铸件。C 样式转换基本上与尝试一系列 C++ 转换序列相同,并采用第一个有效的 C++ 转换,而无需考虑 .毋庸置疑,这功能更强大,因为它结合了所有 、 和 ,但它也不安全,因为它不使用 .dynamic_cast
const_cast
static_cast
reinterpret_cast
dynamic_cast
此外,C 样式强制转换不仅允许你这样做,而且还允许你安全地强制转换为私有基类,而“等效”序列会为此提供编译时错误。static_cast
有些人更喜欢 C 型演员,因为它们简洁。我仅将它们用于数字强制转换,并在涉及用户定义类型时使用适当的 C++ 强制转换,因为它们提供了更严格的检查。
评论
dynamic_cast
static_cast
Derived
member m
dynamic_cast
dynamic_cast
仅支持指针和引用类型。如果类型是指针,则无法强制转换,则返回,如果类型是引用类型,则引发异常。因此,可用于检查对象是否属于给定类型,不能(您最终会得到一个无效的值)。NULL
dynamic_cast
static_cast
C型(和其他)演员表已在其他答案中介绍。
评论
静态转换
静态强制转换在兼容类型之间执行转换。它类似于 C 型演员表,但限制性更强。例如,C 样式强制转换将允许整数指针指向字符。char c = 10; // 1 byte
int *p = (int*)&c; // 4 bytes
由于这会导致 4 字节指针指向 1 字节的已分配内存,因此写入此指针将导致运行时错误或覆盖某些相邻内存。
*p = 5; // run-time error: stack corruption
与 C 样式强制转换相比,静态强制转换将允许编译器检查指针和指针数据类型是否兼容,这允许程序员在编译期间捕获此不正确的指针赋值。
int *q = static_cast<int*>(&c); // compile-time error
重新诠释演员表
若要强制指针转换,与 C 样式强制转换在后台执行的方式相同,将改用重新解释强制转换。
int *r = reinterpret_cast<int*>(&c); // forced conversion
此强制转换处理某些不相关类型之间的转换,例如从一个指针类型到另一个不兼容的指针类型。它只会执行数据的二进制复制,而不会更改基础位模式。请注意,这种低级操作的结果是特定于系统的,因此不可移植。如果不能完全避免,则应谨慎使用。
动态投射
此类型仅用于将对象指针和对象引用转换为继承层次结构中的其他指针或引用类型。它是唯一确保指向的对象可以转换的强制转换方法,方法是执行运行时检查指针是否引用了目标类型的完整对象。要使此运行时检查成为可能,对象必须是多态的。也就是说,该类必须定义或继承至少一个虚函数。这是因为编译器将仅为此类对象生成所需的运行时类型信息。
动态转换示例
在下面的示例中,使用动态强制转换将指针转换为指针。此派生到基的转换成功,因为 Child 对象包含一个完整的 Base 对象。MyChild
MyBase
class MyBase
{
public:
virtual void test() {}
};
class MyChild : public MyBase {};
int main()
{
MyChild *child = new MyChild();
MyBase *base = dynamic_cast<MyBase*>(child); // ok
}
下一个示例尝试将指针转换为指针。由于 Base 对象不包含完整的 Child 对象,因此此指针转换将失败。为了指示这一点,动态强制转换返回一个 null 指针。这提供了一种方便的方法来检查转换在运行时是否成功。MyBase
MyChild
MyBase *base = new MyBase();
MyChild *child = dynamic_cast<MyChild*>(base);
if (child == 0)
std::cout << "Null pointer returned";
如果转换的是引用而不是指针,则动态强制转换将因引发异常而失败。这需要使用语句进行处理。bad_cast
try-catch
#include <exception>
// …
try
{
MyChild &child = dynamic_cast<MyChild&>(*base);
}
catch(std::bad_cast &e)
{
std::cout << e.what(); // bad dynamic_cast
}
动态或静态转换
使用动态强制转换的优点是,它允许程序员在运行时检查转换是否成功。缺点是执行此检查会产生性能开销。因此,在第一个示例中最好使用静态强制转换,因为派生到基的转换永远不会失败。
MyBase *base = static_cast<MyBase*>(child); // ok
但是,在第二个示例中,转换可能会导致运行时错误。如果对象包含实例,则会发生错误,但如果它包含实例,则不会发生错误。在某些情况下,这可能要到运行时才能知道。在这种情况下,动态强制转换是比静态强制转换更好的选择。MyBase
MyBase
MyChild
// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);
如果使用静态强制转换而不是动态强制转换来执行基数到派生转换,则转换不会失败。它将返回一个引用不完整对象的指针。取消引用此类指针可能会导致运行时错误。
// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);
// Incomplete MyChild object dereferenced
(*child);
Const 演员表
这个主要用于添加或删除变量的修饰符。const
const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const
尽管强制转换允许更改常量的值,但这样做仍然是可能导致运行时错误的无效代码。例如,如果常量位于只读内存的某个部分中,则可能会发生这种情况。const
*nonConst = 10; // potential run-time error
const
相反,当存在采用非常量指针参数的函数时,主要使用 cast,即使它不会修改指针。
void print(int *p)
{
std::cout << *p;
}
然后,可以使用强制转换向函数传递常量变量。const
print(&myConst); // error: cannot convert
// const int* to int*
print(nonConst); // allowed
评论
std::bad_cast
定义于<typeinfo>
MyBase *base = child; // ok
评论