普通演员vs.static_castvs.dynamic_cast

我已经写了将近20年的C和C++代码,但是这些语言有一个方面我从来没有真正理解过。

MyClass *m = (MyClass *)ptr;

到处都是,但是似乎还有另外两种类型的转换,我不知道其中的区别。下面几行代码有什么区别?

MyClass *m = (MyClass *)ptr;MyClass *m = static_cast<MyClass *>(ptr);MyClass *m = dynamic_cast<MyClass *>(ptr);
846640 次浏览

dynamic_cast具有运行时类型检查,仅适用于引用和指针,而static_cast不提供运行时类型检查。有关完整信息,请参阅MSDN文章static_cast运算符

你应该看看文章C++编程/类型转换

它包含了对所有不同转换类型的良好描述。

const_cast

const_cast(表达式)const_cast<>()用于添加/删除变量的const(ness)(或易失性)。

static_cast

static_cast(表达式)static_cast<>()用于在整数类型。'例如'char->long,int->短等。

静态转换也用于将指针转换为相关类型,例如示例将val*转换为适当的类型。

dynamic_cast

动态转换用于在运行时转换指针和引用,通常用于向上或向下转换指针或引用继承链(继承层次结构)。

dynamic_cast(表达式)

目标类型必须是指针或引用类型,并且表达式必须计算为指针或引用。动态转换工作只有当表达式引用的对象类型是与目标类型兼容,并且基类至少有一个虚成员函数。如果没有,以及正在强制转换的表达式类型是一个指针,如果对引用进行动态转换,则返回NULL失败,则抛出bad_cast异常。当它没有失败时,动态cast返回指向对象的目标类型的指针或引用引用的表达式。

reinterpret_cast

重新解释转换只是将一种类型按位转换为另一种类型。任何指针或整数类型可以通过重新解释转换转换为任何其他类型,很容易被误用。例如,重新解释铸造一可能会不安全地将整数指针转换为字符串指针。

C样式强制转换合并const_cast、static_cast和reinterpret_cast。

我希望C++没有C风格的强制转换。C++强制转换正确地脱颖而出(他们应该;强制转换通常表明做了坏事),并正确区分强制转换执行的不同类型的转换。它们还允许编写类似的函数,例如提升::lexical_cast,从一致性的角度来看,这是非常好的。

仅供参考,我相信引用Bjarne Stroustrup的话说,要避免C风格的转换,如果可能的话,你应该使用static_cast或dynamic_cast。

Barne Stroustrup的C++风格常见问题解答

不管你怎么听我的建议。我远不是一个C++大师。

避免使用C样式转换。

C风格的强制转换是const和重新解释强制转换的混合,很难在代码中找到和替换。C++应用程序程序员应避免C风格强制转换。

static_cast

static_cast用于基本上想要反转隐式转换的情况,有一些限制和添加。static_cast不执行运行时检查。如果您知道自己引用的是特定类型的对象,则应该使用这一点,因此检查是不必要的。示例:

void func(void *data) {// Conversion from MyClass* -> void* is implicitMyClass *c = static_cast<MyClass*>(data);...}
int main() {MyClass c;start_thread(&func, &c)  // func(&c) will be called.join();}

在此示例中,您知道您传递了一个MyClass对象,因此不需要运行时检查来确保这一点。

dynamic_cast

当您不知道对象的动态类型是什么时,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_castdynamic_cast始终有效,并且没有任何转换,因为“向上转换”是隐式转换(假设基类是可访问的,即它是public继承)。

普通铸件

这些也被称为C风格转换。C风格转换基本上与尝试一系列C++转换相同,并采取第一个有效的C++转换,而不考虑dynamic_cast。不用说,这更强大,因为它结合了const_caststatic_castreinterpret_cast的所有内容,但它也不安全,因为它不使用dynamic_cast

此外,C风格的强制转换不仅允许您这样做,而且还允许您安全地强制转换为私有基类,而“等效”static_cast序列会为此给您一个编译时错误。

有些人更喜欢C风格的强制转换,因为它们简洁。我仅将它们用于数字强制转换,并在涉及用户定义类型时使用适当的C++强制转换,因为它们提供更严格的检查。

dynamic_cast仅支持指针和引用类型。如果类型是指针,则返回NULL,如果类型是引用类型,则抛出异常。因此,dynamic_cast可用于检查对象是否属于给定类型,static_cast不能(您只会以无效值结束)。

C风格(和其他)转换已在其他答案中介绍。

静态铸造

静态强制转换执行兼容类型之间的转换。它类似于C风格强制转换,但更具限制性。例如,C风格强制转换允许整数指针指向char。
char c = 10;       // 1 byteint *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指针。这种派生到基的转换成功,因为Children对象包含一个完整的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对象不包含完整的Children对象,因此指针转换将失败。为此,动态转换返回一个空指针。这提供了一种方便的方法来检查转换是否在运行时成功。

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 objectMyChild *child = dynamic_cast<MyChild*>(base);

如果使用静态强制转换而不是动态强制转换执行基到派生转换,则转换不会失败。它会返回一个引用不完整对象的指针。取消引用此类指针会导致运行时错误。

// Allowed, but invalidMyChild *child = static_cast<MyChild*>(base); 
// Incomplete MyChild object dereferenced(*child);

constcast

这个主要用于添加或删除变量的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

来源和更多的解释