普通演员对比static_cast 对比动态转换[重复]
题
这个问题在这里已经有答案了:
我编写 C 和 C++ 代码已有近二十年了,但这些语言的某些方面我从未真正理解过。我显然使用过常规演员表,即
MyClass *m = (MyClass *)ptr;
到处都是,但似乎还有其他两种类型的演员,我不知道有什么区别。下面几行代码有什么区别?
MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);
解决方案
静态类型转换
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
当您不知道对象的动态类型是什么时非常有用。如果引用的对象不包含转换为基类的类型(当转换为引用时, 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
}
“向上转换”(转换为基类)对于两者始终有效 static_cast
和 dynamic_cast
, ,并且也没有任何强制转换,因为“向上强制转换”是隐式转换。
常规演员
这些类型转换也称为 C 型类型转换。C 风格的强制转换与尝试一系列 C++ 强制转换序列基本相同,并采用第一个有效的 C++ 强制转换,而无需考虑 dynamic_cast
. 。不用说,这更强大,因为它结合了所有 const_cast
, static_cast
和 reinterpret_cast
, ,但它也不安全,因为它不使用 dynamic_cast
.
此外,C 风格的强制转换不仅允许您执行此操作,还允许您安全地强制转换为私有基类,而“等效” static_cast
序列会给你一个编译时错误。
有些人更喜欢 C 风格的强制转换,因为它很简洁。我仅将它们用于数字转换,并在涉及用户定义类型时使用适当的 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
此转换处理某些不相关类型之间的转换,例如从一种指针类型到另一种不兼容的指针类型。它将简单地执行数据的二进制副本,而不改变底层的位模式。请注意,此类低级操作的结果是特定于系统的,因此不可移植。如果无法完全避免,则应谨慎使用。
动态演员表
这个仅用于将对象指针和对象引用转换为继承层次结构中的其他指针或引用类型。它是唯一的强制转换,通过执行运行时检查指针是否引用目标类型的完整对象,确保可以转换所指向的对象。为了使这种运行时检查成为可能,对象必须是多态的。也就是说,该类必须定义或继承至少一个虚函数。这是因为编译器只会为此类对象生成所需的运行时类型信息。
动态演员表示例
在下面的示例中,使用动态转换将 MyChild 指针转换为 MyBase 指针。此派生到基类的转换会成功,因为 Child 对象包含完整的 Base 对象。
class MyBase
{
public:
virtual void test() {}
};
class MyChild : public MyBase {};
int main()
{
MyChild *child = new MyChild();
MyBase *base = dynamic_cast<MyBase*>(child); // ok
}
下一个示例尝试将 MyBase 指针转换为 MyChild 指针。由于 Base 对象不包含完整的 Child 对象,因此该指针转换将失败。为了表明这一点,动态转换返回一个空指针。这提供了一种在运行时检查转换是否成功的便捷方法。
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 int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const
尽管 const 强制转换允许更改常量的值,但这样做仍然是无效代码,可能会导致运行时错误。例如,如果常量位于只读存储器的某个部分,则可能会发生这种情况。
*nonConst = 10; // potential run-time error
主要当有一个函数接受非常量指针参数时使用 const 强制转换,即使它不修改指针对象。
void print(int *p)
{
std::cout << *p;
}
然后可以使用 const 强制转换向该函数传递常量变量。
print(&myConst); // error: cannot convert
// const int* to int*
print(nonConst); // allowed
你应该看看这篇文章 C++ 编程/类型转换.
它包含对所有不同演员类型的良好描述。以下摘自上述链接:
常量类型转换
const_cast(expression)const_cast <>()用于添加/删除变量的const(ness)(或挥发性)。
静态类型转换
static_cast(expression)static_cast <>()用于在整数类型之间施放。'eg'char-> long,int-> short等。
静态铸件还用于将指针铸成相关类型,例如将void*铸造为适当的类型。
动态转换
动态铸件用于在运行时转换指针和参考,通常是为了在继承链(继承层次结构)上施放指针或参考。
动态转换(表达式)
目标类型必须是指针或参考类型,并且表达式必须评估指针或参考。仅当表达式引用的对象类型与目标类型兼容并且基类具有至少一个虚拟成员函数时,动态铸件才起作用。如果不是,并且表达式的类型是指针,则返回空,如果引用失败的动态铸造,则会抛出bad_cast异常。当它没有失败时,动态铸造将返回目标类型的指针或引用到表达式所引用的对象。
重新解释_cast
重新解释强制转换只是将一种类型按位转换为另一种类型。任何指针或积分类型都可以用重新诠释铸件铸造给其他任何其他指针类型,很容易滥用。例如,在重新诠释的情况下,铸造一个可能不安的是将整数指针铸成字符串指针。
仅供参考,我相信 Bjarne Stroustrup 曾说过要避免 C 风格的强制转换,并且如果可能的话应该使用 static_cast 或dynamic_cast。
Barne Stroustrup 的 C++ 风格常见问题解答
无论你愿意什么,都接受这个建议。我距离 C++ 大师还很远。
避免使用 C 型强制转换。
C 风格的强制转换是 const 和重新解释强制转换的混合,并且很难在代码中查找和替换。C++ 应用程序程序员应该避免 C 风格的强制转换。
C 风格的强制转换合并了 const_cast、static_cast 和 reinterpret_cast。
我希望 C++ 没有 C 风格的强制转换。C++ 强制转换正确地脱颖而出(它们应该如此;强制转换通常表示做了坏事)并正确区分强制转换执行的不同类型的转换。它们还允许编写类似的函数,例如boost::lexical_cast,从一致性的角度来看这非常好。
dynamic_cast
具有运行时类型检查并且仅适用于引用和指针,而 static_cast
不提供运行时类型检查。有关完整信息,请参阅 MSDN 文章 static_cast 运算符.
dynamic_cast
仅支持指针和引用类型。它返回 NULL
如果类型是指针,则强制转换不可能;如果类型是引用类型,则抛出异常。因此, dynamic_cast
可用于检查对象是否属于给定类型, static_cast
不能(您最终会得到一个无效值)。
其他答案中已经涵盖了 C 风格(和其他)强制转换。