Non-Profit, International

Spirit unsterblich.

C++ static_cast,reinterpret_cast 和 const_cast

字数统计:2076 blog

C++11 开始,弃用了 C 风格的显示类型转换,额外提供了 3 个模板来完成不同的类型转换,便于调试。

static_cast

static_cast 常见用途有两点,第一点是显示转换所有隐式转换,第二点是在相关的类(类型)之间进行转换。

具体来说,static_cast 支持以下转换:

初始化转换


int n = static_cast<int>(3.14);
std::vector<int> v = static_cast<std::vector<int>>(10);

static_cast 实际上可能会有潜在的临时变量生成,但是 C++17 开始强制要求返回值优化(ROV),即将返回值直接写入接收的变量,而不产生额外的复制。

其中包含返回左值和返回纯右值,注意不要在返回值使用 std::move,因为 std::move 返回将亡值,不利于编译器进行优化;并且不能返回右值引用,因为会导致悬垂引用。

派生类转换为基类的引用

这个操作是安全的,也可以反过来,但是会变为不安全。


struct B {
    int m = 0;
    void hello() const {
        std::cout << "Hello world,这里是 B!" << std::endl;
    }
};
struct D : B {
    void hello() const {
        std::cout << "Hello world,这里是 D!" << std::endl;
    }
};

int main()
{
    D d;
    B& br = d; // 通过隐式转换向上转型
    br.hello();
    D& another_d = static_cast<D&>(br); // 向下转型
    another_d.hello();
    B& another_b = static_cast<D&>(another_d);
    another_b.hello();
}

将左值转换为右值引用

这实际上就是 std::move 的实现(通过模板)。


int main()
{
    std::vector<int> v1 = static_cast<std::vector<int>>(10);
    std::vector<int> v2 = static_cast<std::vector<int> &&>(v);
    std::cout << "移动后,v.size() = " << v.size() << std::endl;
}

移动后 v1 的大小会变为 0,换句话说 v1 的所有权交给了 v2。

弃值表达式

即将函数的返回值丢弃,但是显式的用 static_cast 表示,这是 C++ 中唯一能传递 void 的地方。


static_cast<void>(v2.size());

在 void* 之间进行转换(不安全)


int n = 3.14;
void* nv = &n;
int* ni = static_cast<int*>(nv);
nv = static_cast<void*>(ni);
std::cout << "*ni = " << *ni << std::endl;

将数组显式退化到指针,并且可以伴随进行相关类型的转换


D a[10];
B* dp = static_cast<B*>(a);

限定作用域枚举转换为其他类型(必须)和非限定枚举的隐式转换的显式表示


enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };

E e = E::ONE;
int one = static_cast<int>(e);
std::cout << one << std::endl;
E e2 = static_cast<E>(one);
EU eu = static_cast<EU>(e2);

类的成员函数的指针转换


int main()
{
    D d;
    B& br = d;
    int D::*pm = &D::m;
    std::cout << br.*static_cast<int B::*>(pm) << std::endl;
    int B::*wm = &B::m;
    std::cout << d.*static_cast<int D::*>(wm) << std::endl;
}

const_cast

const_cast 的目的是转换 const 和 volatile,可用于去除或者添加(通常是去除)

const_cast 使得到非 const 类型的引用或指针能够实际指代 const 对象,或到非 volatile 类型的引用或指针能够实际指代 volatile 对象。通过转换后的非 const 访问路径修改 const 对象和通过非 volatile 泛左值涉指 volatile 对象是未定义行为。

const_cast 是让编译器进行一个“宽松的”审查,从而忽略 const 标识,并不会带来额外性能上的开销。


struct type {
    int i;
 
    type(): i(3) {}
 
    void f(int v) const {
        // this->i = v;                 // 编译错误:this 是指向 const 的指针
        const_cast<type*>(this)->i = v; // 只要该对象 (i)不是 const 就 OK
    }
};

int main() 
{
    int i = 3;                 // 不声明 i 为 const
    const int& rci = i; 
    const_cast<int&>(rci) = 4; // OK:修改 i
    std::cout << "i = " << i << '\n';
 
    type t; // 假如这是 const type t,则 t.f(4) 会是未定义行为
    t.f(4);
    std::cout << "type::i = " << t.i << '\n';
 
    const int j = 3; // 声明 j 为 const
    [[maybe_unused]]
    int* pj = const_cast<int*>(&j);
    // *pj = 4;      // 未定义行为
 
    [[maybe_unused]]
    void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
    // const_cast<void(type::*)(int)>(pmf);   // 编译错误:const_cast 不能用于成员函数指针
}

reinterpret_cast

reinterpret_cast 大部分作用是在二进制的基础上对数据进行重新解释,即对类型无关的指针进行转换,但是不能代替 const_cast。

reinterpret_cast 和 const_cast 一样没有副作用,同时 reinterpret_cast 有些时候相当于两步 static_cast(即转换为 void* 再从 void* 转换为其他类型的指针)。

reinterpret_cast 的安全性完全由程序员负责,很有可能发生不安全的访问(例如将 uint32_t 解释为 uint64_t,再去获得 uint64_t 的数据,可能会发生访问越界以及段错误)。

 还可以使用 reinterpret_cast 进行反退化操作:


int main()
{
    int(*n2)[3][2] = reinterpret_cast<int(*)[3][2]>(new int[3][2]);
    (*n2)[0][0] = 1;
    int n3[3][2];
    auto *n4 = &n3;
    std::cout << typeid(n2).name() << std::endl;
    std::cout << typeid(*n2).name() << std::endl;
    std::cout << typeid(n4).name() << std::endl;
    std::cout << typeid(*n4).name() << std::endl;
    delete n2;
    n2 = n4;
}

参考:

若无特殊声明,本人原创文章以 CC BY-SA 3.0 许可协议 提供。