C++中static_cast和dynamic_cast强制类型转换
1.static_cast关键字(编译时类型检查)
用法:
- static_cast < type-id > ( expression ),该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性
它主要有如下几种用法:
- (1)用于基本数据类型之间的转换,如把int转换为char,把int转换成enum,但这种转换的安全性需要开发者自己保证(这可以理解为保证数据的精度,即程序员能不能保证自己想要的程序安全),如在把int转换为char时,如果char没有足够的比特位来存放int的值(int>127或int<-127时),那么static_cast所做的只是简单的截断,及简单地把int的低8位复制到char的8位中,并直接抛弃高位。
- (2)把空指针转换成目标类型的空指针
- (3)把任何类型的表达式类型转换成void类型
- (4)用于类层次结构中父类和子类之间指针和引用的转换。
对于static_cast,上行转换时安全的,而下行转换时不安全的
因为static_cast的转换时粗暴的,它仅根据类型转换语句中提供的信息(尖括号中的类型)来进行转换,这种转换方式对于上行转换,由于子类总是包含父类的所有数据成员和函数成员,因此从子类转换到父类的指针对象可以没有任何顾虑的访问其(指父类)的成员。
而对于下行转换为什么不安全,是因为static_cast只是在编译时进行类型检查,没有运行时的类型检查,具体原理在dynamic_cast中说明。
2.dynamic_cast关键字(运行时类型检查)
用法:
【格式】dynamic_cast<type_id>(expression) :
该运算符把expression转换为type_id 类型, type_id 可以为类的指针、类的引用、void*,expression为对应的指针或引用.
- dynamic_cast是四个强制类型转换操作符中最特殊的一个,它支持运行时识别指针或引用。
- 首先,dynamic_cast依赖于RTTI信息,其次,在转换时,dynamic_cast会检查转换的source对象是否真的可以转换成target类型,
这种检查不是语法上的,而是真实情况的检查。 - 若对指针进行dynamic_cast,失败返回nullptr,成功返回正常cast后的对象指针;
- 若对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用
dynamic_cast,主要用于类层次间的转换:
-
上行转换(子类转父类),转换安全,成功返回类对象指针, 此时和static_cast 作用一样。
-
下行转换(父类转子类), 父类中要有虚函数,否则编译器报错。转换分几种情况:
a. 父类指针指向子类对象,转换安全, dynamic_cast返回类对象指针。
b.父类指针指向父类对象,转换不安全,dynamic_cast 返回nullptr。
此时若使用static_cast, 返回非空指针,更不安全。 -
eg:
#include <iostream>
using namespace std;
class A{
public:
A(){}
virtual void eat()
{
std::cout<<"A::eat()"<<std::endl;
}
void sing(){
std::cout<<"A::sing()"<<std::endl;
}
~A(){};
};
class B : public A{
public:
B() {};
void eat()
{
std::cout<<"B::eat()"<<std::endl;
}
void sing()
{
std::cout<<"B::sing()"<<std::endl;
}
~B(){}
int data;
};
int main()
{
A* a= nullptr;
B* b = nullptr;
A aa;
B bb;
b = &bb;
// 安全的向上转型
a = dynamic_cast<A*>(b);
a->sing();
a->eat();
std::cout<<"===="<<std::endl;
// 向下转型,向下转型时,父类必须含有虚函数,否则编译报错
//父类指针指向子类对象,这样的dynamic_cast向下转换才是安全的
a = &bb;
b = dynamic_cast<B*>(a);
b->sing();//yes
b->eat();//yes
std::cout<<"===="<<std::endl;
//父类指针指向父类对象,这样的dynamic_cast向下转换是不安全的
a = &aa;
b = dynamic_cast<B*>(a);
if (b == nullptr)
{
std::cout<<"the call is not safe "<<std::endl;
}
b->sing();//yes
b->eat();//Segmentation fault
std::cout<<"===="<<std::endl;
//static_cast不做类型检查,不返回nullptr,不安全
b = static_cast<B*>(a);
if (b == nullptr)
{
std::cout<<"second the call is not safe "<<std::endl;
}
b->sing();//yes
b->eat();//居然调用的基类的eat,不对
return 0;
}
3. std::shared_ptr 智能指针强转
当我们用“裸”指针进行类层次上的上下行转换时,可以使用dynamic_cast。
- 当然我们也可以使用static_cast,只是dynamic_cast在进行下行转换的时候(即基类到派生类)具有类型检查功能,而static_cast没有。因此存在安全问题。
当我们使用智能指针时,如果需要进行类层次上的上下行转换时,可以使用std::static_pointer_cast()、std::dynamic_pointer_cast、std::const_pointer_cast()和std::reinterpret_pointer_cast()。
- 但是要求:转换的是智能指针std::shared_ptr,返回的也是std::shared_ptr类型。
1、std::static_pointer_cast():当指针是智能指针时候,向上转换,用static_cast 则转换不了,此时需要使用static_pointer_cast。
2、std::dynamic_pointer_cast():当指针是智能指针时候,向下转换,用dynamic_cast 则转换不了,此时需要使用dynamic_pointer_cast。
3、std::const_pointer_cast():功能与std::const_cast()类似
4、std::reinterpret_pointer_cast():功能与std::reinterpret_cast()类似
- 基类和派生类的智能指针转换要使用std::dynamic_pointer_cast和std::static_pointer_cast。由于std::dynamic_pointer_cast和dynamic_cast原理一样,std::static_pointer_cast和static_cast原理一样
#include <iostream>
#include <memory>
struct Base
{
int a;
virtual void f() const { std::cout << "I am base!\n";}
virtual ~Base(){}
};
struct Derived : Base
{
void f() const override
{ std::cout << "I am derived!\n"; }
~Derived(){}
};
int main(){
auto basePtr = std::make_shared<Base>();
std::cout << "Base pointer says: ";
basePtr->f();
auto derivedPtr = std::make_shared<Derived>();
std::cout << "Derived pointer says: ";
derivedPtr->f();
// static_pointer_cast to go up class hierarchy
basePtr = std::static_pointer_cast<Base>(derivedPtr);
std::cout << "Base pointer to derived says: ";
basePtr->f();
// dynamic_pointer_cast to go down/across class hierarchy
auto downcastedPtr = std::dynamic_pointer_cast<Derived>(basePtr);
if(downcastedPtr)
{
std::cout << "Downcasted pointer says: ";
downcastedPtr->f();
}
// All pointers to derived share ownership
std::cout << "Pointers to underlying derived: "
<< derivedPtr.use_count()
<< "\n";
}
#include <iostream> // std::cout std::endl
#include <memory> // std::shared_ptr std::dynamic_pointer_cast std::static_pointer_cast
class base {
public:
virtual ~base(void) = default;
};
class derived : public base {};
class test : public base {};
int main(void) {
std::cout << std::boolalpha;
// 两个不同的派生类对象
auto derivedobj = std::make_shared<derived>();
auto testobj = std::make_shared<test>();
// 隐式转换 derived->base
std::shared_ptr<base> pointer1 = derivedobj;
// static_pointer_cast derived->base
auto pointer2 = std::static_pointer_cast<base>(derivedobj);
// dynamic_pointer_cast base->derived
auto pointer3 = std::dynamic_pointer_cast<derived>(pointer1);
std::cout << (pointer3 == nullptr) << std::endl;
// dynamic_pointer_cast base->derived
auto pointer4 = std::dynamic_pointer_cast<test>(pointer1);
std::cout << (pointer4 == nullptr) << std::endl;
return 0;
}
#include <memory>
#include <cassert>
#include <cstdint>
int main()
{
std::shared_ptr<int> foo;
std::shared_ptr<const int> bar;
foo = std::make_shared<int>(10);
bar = std::const_pointer_cast<const int>(foo);
std::cout << "*bar: " << *bar << std::endl;
*foo = 20;
std::cout << "*bar: " << *bar << std::endl;
std::shared_ptr<std::int8_t[]> p(new std::int8_t[4]{1, 1, 1, 1});
std::shared_ptr<std::int32_t[]> q = std::reinterpret_pointer_cast<std::int32_t[]>(p);
std::int32_t r = q[0];
std::int32_t x = (1 << 8) | (1 << 16) | (1 << 24) | 1;
assert(r == x);
return 0;
}
参考: