常规转换 vs. static_cast vs. dynamic_cast

2034

我已经写了将近20年的C和C++代码,但是这些语言中有一个方面我从来没有真正理解过。显然我使用过常规转换,即

MyClass *m = (MyClass *)ptr;

代码中有很多转换,但似乎还有两种其他类型的转换,我不知道它们之间的区别。以下代码行有什么区别?

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

78
在C++中,我不会称遗留的C风格转换为“常规转换”,因为它确实不是。特别是对于类,你通常不应该使用它,使用它太容易出错了。使用它表明一个C程序员已经转向C++,但还没有完全学会C++。 - hyde
7
@Vladp 如果你还在疑惑,或者其他人正在阅读并感到疑惑。(另外,为了记录,关闭这个问题的不是一个版主,而是一个带有重复标记的用户。) - anon
5
请注意,链接的问题获得了更多的赞同票,答案也获得了更多的赞同票。此外,链接的问题还包括一些不错的非理论示例。(另外,链接的问题没有错误地将 C 风格的类型转换语法称为“正常转换”)。 - Trevor Boyd Smith
8个回答

1843

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在你不知道对象的动态类型时非常有用。如果所引用的对象不包含作为基类转换的类型(当你转换为引用时,在这种情况下会抛出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进行“向上转型”(转换为基类)始终是有效的,而且不需要进行任何强制类型转换,因为“向上转型”是一种隐式转换(假设基类是可访问的,即是公共继承)。

常规转换

这些转换也称为C风格转换。C风格转换基本上相当于尝试使用一系列的C++转换序列,并采用第一个能够工作的C++转换,而不考虑dynamic_cast。毫无疑问,它更加强大,因为它结合了所有的const_caststatic_castreinterpret_cast,但也不安全,因为它不使用dynamic_cast

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

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


14
请参见Boost库的另外两个类型转换函数:http://www.boost.org/doc/libs/1_47_0/libs/conversion/cast.htm#Polymorphic_cast - Neil G
3
你确定C风格的强制类型转换能够“安全地”将对象转换为私有基类吗?当私有基类是唯一的/基类/时,我能看出这样做是可行的,但在虚拟/多重继承的情况下呢?我假设C风格的强制类型转换不进行指针操作。 - Joseph Garvin
2
@JohannesSchaub-litb,使用旧的C风格转换而不是使用C++转换也会导致一些开销,这是真的吗? - xcrypt
1
@haxpor C风格的转换没有动态转换的开销--它可能会进行指针调整,基本上只是对指针进行加或减。它将调用内置的(int<->float)和用户定义的转换,后者可以任意复杂。 - Stephen Lin
4
如果在动态类型转换的部分中进行下行转换是无效的,你能否更详细地解释一下原因?假设Derived有一个成员变量m,如果不能使用dynamic_cast,该如何访问它?请提供解决方案。我会尽力使翻译保持原意并更易懂。 - ted
显示剩余11条评论

283

静态转换

静态转换在兼容的类型之间进行转换。它类似于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风格的转换在后台所做的那样,应该使用reinterpret cast。
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 对象,这个指针转换将失败。为了表示这一点,dynamic_cast 返回一个空指针。这提供了一种方便的方法,在运行时检查转换是否成功。
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 cast

这个主要用于添加或移除变量的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 is defined in <typeinfo> - Evg
1
从子类到基类,不需要强制转换: MyBase *base = child; // 可以 - Isidoro Ghezzi
7
在我看来,最好的答案非常简单而且清晰明了。 - Mohammed Noureldin
4
我认为这应该是最佳答案。 - Jazzy
这是最好的答案。 - Olga Pshenichnikova
我在GCC和Visual Studio上运行了这行代码"*p = 5; // 运行时错误:堆栈破坏",却没有出现运行时错误。!!?? - Azzurro94

91

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

它包含了对所有不同类型的转换的良好描述。以下内容摘自上面的链接:

const_cast

const_cast(expression) 用于添加/删除变量的constness(或volatile性)。

static_cast

static_cast(expression) 用于在整数类型之间进行类型转换。例如 char->long, int->short 等。

Static cast还用于将指针转换为相关类型,例如将void*转换为适当的类型。

dynamic_cast

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

dynamic_cast(expression)

目标类型必须是指针或引用类型,并且表达式必须求值为指针或引用。仅当表达式所引用的对象类型与目标类型兼容并且基类至少有一个虚成员函数时,dynamic_cast才起作用。如果不是这样,并且被转换的表达式类型是指针,则返回NULL;如果对引用进行动态转换失败,则抛出bad_cast异常。当它不失败时,dynamic_cast会将指向expression所引用对象的指针或引用返回到目标类型的对象。

reinterpret_cast

reinterpret_cast只是按位转换一种类型到另一种类型。任何指针或整数类型都可以使用reinterpret_cast转换为任何其他类型,因此很容易被误用。例如,使用reinterpret_cast,可以不安全地将整数指针转换为字符串指针。


35

供您参考,我相信Bjarne Stroustrup曾说过应该避免使用C-style cast,而应尽可能使用static_cast或dynamic_cast。

Barne Stroustrup的C++风格FAQ

根据自己的需求采纳这个建议。我离成为C++大师还有很长的路要走。


14
是的,因为明确标记并有意限定于定义明确角色的 C++ 强制类型转换比 C 语言强制类型转换更加“可怕”,后者只是盲目地尝试多种类型的转换,直到_任何_一种有效为止,而不考虑是否合理... 这个好。 - underscore_d
实际上,如果你阅读过他的常见问题解答,Stroustrup推荐你完全避免使用转换操作。在他关于static_cast的章节中第一句话是:“转换通常最好避免使用。” - Bill Weinman
@BillWeinman 实际上你无法完全避免强制类型转换(就我而言,“最好避免”这个措辞也允许这样做)。一旦你将自己的代码与API或不同的API相互接口,往往会出现类型不完全匹配的情况,你将不得不使用强制类型转换。这在老的和有机生长的API中尤其如此。WinAPI就是一个典型的例子。 - blubberdiblub

28

避免使用C风格转换。

C风格转换是const和reinterpret转换的混合体,在代码中很难查找和替换。C++应用程序员应避免使用C风格转换。


15

C-style转换混淆了const_cast、static_cast和reinterpret_cast。

我希望C++没有C-style转换。 C++ 转换有明显的特征(正如它们应该有的那样; 转换通常表示正在执行不好的操作),并且正确区分转换执行的不同类型。 它们还允许编写类似的函数,例如 boost::lexical_cast,这在一致性方面非常好。


13

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


13

dynamic_cast 只支持指针和引用类型。如果转换不可能,则返回 NULL(对于指针类型),或者抛出异常(对于引用类型)。因此,dynamic_cast 可以用于检查对象是否为特定类型,而 static_cast 不能(你最终只会得到一个无效值)。

C风格(以及其他)的转换在其他答案中已经讨论过了。


1
你最终会得到一个无效的值和未定义的行为。也就是说,即使你不使用该值,程序也会出现错误。 - curiousguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接