pointers - VS常规转换 static_cast VS dynamic_cast

  显示原文与译文双语对照的内容

我已经编写了将近二十年的C++ 和代码,但是这些语言的一个方面我从未真正理解过。 我显然使用了常规的强制转换 i.e.


MyClass *m = (MyClass *)ptr;

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


MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);

时间:

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 用于不知道对象动态类型的情况。 如果向下和参数类型不多态,则不能使用 dynamic_cast 。 一个示例:


if(JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
. . .
} else if(ExprStm *e = dynamic_cast<ExprStm*>(&stm)) {
. . .
}

dynamic_cast 浇注,以返回一个空指针,如果引用的对象是否不包含该类型作为基类( 当你转换到引用时,在这种情况下抛出 bad_cast 异常) 。

以下代码无效,因为 Base 不是多态( 它不包含虚函数):


struct Base { };
struct Derived : Base { };
int main() {
 Derived d; Base *b = &d;
 dynamic_cast<Derived*>(b);//Invalid
}

"up-cast"对于 static_castdynamic_cast 总是有效的,而且没有任何强制转换,作为"up-cast"是隐式转换。

常规强制转换

这些类型转换也称为 C-style cast 。 C-style转换基本上等同于尝试一系列的C++ 类型转换,并采用第一个 C++ 转换,而不考虑 dynamic_cast 。 不用说,因为它组合了所有的const_caststatic_castreinterpret_cast,这更加强大,但是它也不安全,因为它不使用 dynamic_cast

另外,C-style不仅允许你这样做,而且它们还允许你安全地转换为私有 base-class,而"等效" static_cast 序列将为你提供一个compile-time错误。

有些人喜欢C-style的转换,因为它们的简洁性。 我只将它们用于数字类型转换,当用户定义的类型被涉及时使用适当的C++ 类型转换,因为它们提供更严格的检查。

静态强制转换

static_cast不对所涉及的类型进行任何运行时检查,这意味着除非你知道你正在做什么,否则它们可能非常不安全。 它也只允许在相关类型之间进行转换,比如基和派生之间的指针或者引用,或者基本类型之间的转换,比如long到int或者 int 。

它不允许在根本不同类型之间进行转换,比如在BaseA和BaseB之间进行转换如果它们不是相关的。 这将导致编译时错误。

动态转换

dynamic_cast也会进行运行时检查,如果该实例不能转换为另一个派生类型,它将返回一个空指针。

注意:动态类型转换仅在源类型为多态时有效。否则,编译器会给出错误,如 * 错误: 无法 dynamic_cast'b'( 类型'类别 base*') 来输入'类别 inh1*'( 源类型不是多态) *

英镑示例

如果我们有以下类


class B {};

class D : B {};

然后你可以执行以下操作


B* b = new D();
D* d1 = static_cast<D*>b;//Valid! d1 is a valid and correct pointer to a D
D* d2 = dynamic_cast<D*>b;//Valid! d2 is a valid and correct pointer to a D

在本例中,指针d1和d2都指向正确类型

问题出现在以下示例中:


B* b = new B();
D* d1 = static_cast<D*>b;//Invalid!
D* d2 = dynamic_cast<D*>b;//Valid, but d2 is now a null pointer

现在d1将指向类型为D*的数据段,但实际数据是 B*,并且会导致内存问题和损坏。 另一方面d2将是一个空指针,可以检查并正确处理。

因为dynamic_cast执行运行时类型检查,它也比较慢。

编辑:

由于dynamic_cast可以inCurr附加运行时,所以可以通过指示编译器不包含运行时类型信息来关闭它。

还有其他的转换操作符。

重新解释转换

这是最终的强制转换,它忽略各种类型的安全,允许你将任何内容转换为其他类型,基本上重新指定位模式的类型信息。


int i = 12345;
MyClass* p = reinterpret_cast<MyClass*> i;

它很危险,除非你知道你正在做什么,而且基本上是equivilant的。 像这样;


int i = 0;
void *v = 0;
int c = (int)v;//is valid
int d = static_cast<int>(v);//is not valid, different types
int e = reinterpret_cast<int>(v);//is valid, but very dangerous

然后我们有了 const_cast <>,它删除了变量的const-ness 。

静态类型转换

静态类型转换在兼容类型之间执行转换。 它类似于C-style转换,但更严格。 例如C-style转换允许一个整数指针指向一个字符。


char c = 10;//1 byte
int *p = (int*)&c;//4 bytes

由于这导致一个 4 -byte指针指向 1字节的内存,写入这个指针会导致run-time错误或者覆盖一些相邻的内存。


*p = 5;//run-time error: stack corruption

C-style转换相反,静态类型转换允许编译器检查指针和指针指针数据类型是否兼容,这允许程序员在编译过程中捕捉这个不正确的指针赋值。


int *q = static_cast<int*>(&c);//compile-time error

重新解释转换

要强制指针转换,与C-style在背景中进行的转换相同,请改用reinterpret转换 cast 。


int *r = reinterpret_cast<int*>(&c);//forced conversion

这里转换处理某些不相关类型之间的转换,例如从一个指针类型到另一个不兼容的指针类型。 它只执行数据的二进制复制而不改变底层的位模式。 注意,这种低级操作的结果是 system-specific,因此不是可移植的。 如果不能完全避免,应该小心使用它。

动态强制转换

仅用于将对象指针和对象引用转换为继承层次结构中的其他指针或者引用类型。 通过执行run-time检查指针指向目标类型的完整对象,它是唯一能够确保指向的对象可以转换的唯一类型转换。 对于这个run-time检查,对象必须是多态的。 也就是说,类必须至少定义或者继承一个虚函数。 这是因为编译器只为这些对象生成需要的run-time类型信息。

动态类型转换示例

在下面的示例中,使用动态类型转换将一个MyChild指针转换为一个MyBase指针。 这里derived-to-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指针。 因为基本对象不包含完整的子对象这里指针转换将失败。 为了表明这一点,动态类型转换返回空指针。 这提供了一个方便的方法来检查在run-time期间转换是否成功。


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
}

动态或者静态转换

使用动态转换的优点是它允许程序员检查转换是否在run-time期间成功。 缺点是执行这里检查时有一个性能开销。 由于这个原因,在第一个示例中使用静态类型转换会更好,因为derived-to-base转换永远不会失败。


MyBase *base = static_cast<MyBase*>(child);//ok

但是,在第二个示例中,转换可能成功或者失败。 如果MyBase对象包含MyBase实例,它将失败,如果包含MyChild实例,它将成功。 在某些情况下,这可能直到 run-time 。 在这种情况下,动态类型转换比静态类型转换更好。


//Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);

如果使用静态类型转换而不是动态类型转换执行了base-to-derived转换,则转换不会失败。 它将返回一个指向不完整对象的指针。 取消引用这样一个指针会导致run-time错误。


//Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);

//Incomplete MyChild object dereferenced
(*child);

转换转换

主要用于添加或者删除变量的常量修饰符。


const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst);//removes const

尽管常量转换允许更改常数的值,但这样做仍然是无效的代码,可能导致run-time错误。 例如如果常量位于read-only内存的某一部分。


*nonConst = 10;//potential run-time error

实际上,当有一个带有non-constant指针参数的函数时,主要使用Const转换,尽管它不修改对象指针。


void print(int *p) 
{
 std::cout <<*p;
}

然后,函数可以通过使用常量类型转换来传递常量变量。


print(&myConst);//error: cannot convert 
//const int* to int*

print(nonConst);//allowed

源和更多解释

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

如果类型是指针,则 dynamic_cast 返回空如果类型是指针( 如果类型是引用类型则抛出异常) 。 因此,dynamic_cast 可以用来检查一个对象是否是给定类型,static_cast 不能( 你会得到一个无效的值) 。

另外,在某些情况下,static_cast 是不可能的,比如 具有多重继承:


class Base {};
class Foo : public Base {.. . };
class Bar : public Base {.. . };
class FooBar: public virtual Foo, public virtual Bar {.. . };

FooBar a;
Foo & foo1 = static_cast<Foo &>(a);//Illegal, wont compile
Foo & foo2 = dynamic_cast<Foo &>(a);//Legal

C-style ( 等等) 强制转换已经在其他问题中被覆盖。

...