static_cast 转换

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

使用隐式和用户定义转换的组合来进行类型之间的转换。

语法

static_cast<目标类型 >(表达式 )

返回目标类型 类型的值。

解释

只有下列转换在不移除常量性(或易变性)的场合才能用 static_cast 执行。

1) 如果表达式 是 “cv1 Base” 类型左值且目标类型 是“到 cv2 Derived 的引用”,那么在满足以下所有条件时结果指代表达式 的外围 Derived 类型对象:
  • Derived 是完整类类型。
  • BaseDerived 的基类
  • cv1 是不多于 cv2 的 cv 限定。
如果满足以下任意条件,那么程序非良构:
  • BaseDerived虚基类
  • BaseDerived 的某个虚基类的基类。
  • 不存在从“指向 Derived 的指针”到“指向 Base 的指针”的合法标准转换
如果表达式 实际上不是某个 Derived 类型对象的基类子对象,那么行为未定义。
struct B {};
struct D : B { B b };
 
D d;
B& br1 = d;
B& br2 = d.b;
 
static_cast<D&>(br1); // OK,该左值指代原来的 d 对象
static_cast<D&>(br2); // 未定义行为:子对象 b 不是基类子对象
2) 如果目标类型 是“到 Derived 的右值引用”且表达式 是“(可有 cv 限定的)Base” 类型亡值,其中 BaseDerived 的基类,那么这种转换的结果和约束与 “Base 左值到 Derived 引用”转换的相同。
3) 如果目标类型 是右值引用类型且被引用类型引用兼容表达式 的类型,那么 static_cast泛左值、类纯右值或数组纯右值 (C++17 前)任何左值 (C++17 起)表达式 的值转换为与该表达式指代相同对象,或指代它的基类子对象(取决于目标类型)的亡值。[1]
如果目标类型 是表达式 的不可访问或有歧义的基类,那么程序非良构。
如果表达式 是位域左值,那么它会首先被转换成底层类型的纯右值。
(C++11 起)
4) 如果目标类型 是(可有 cv 限定的)void,那么转换没有结果。在这种情况下,表达式 是弃值表达式
5) 否则,

如果对于某个虚设变量 temp 存在良构声明 目标类型 temp(表达式 );,那么表达式 可以显式转换到目标类型

此类显式转换的效果与在进行该声明和初始化后将 temp 作为转换结果相同。当且仅当该初始化将表达式 作为左值 (C++11 前)泛左值 (C++11 起)使用时,转换才会将它作为作为左值 (C++11 前)泛左值 (C++11 起)使用。

(C++17 前)

如果满足以下任意条件,那么表达式 可以显式转换到目标类型

  • 存在从表达式 到目标类型 的隐式转换序列。
  • 对从表达式 直接初始化目标类型 对象或引用的重载决议会找到至少一个可行函数。
  • 目标类型 是有首元素 x聚合体类型,并且存在从表达式 到 x 的类型的隐式转换序列。
(C++20 起)

此类显式转换定义如下:

  • 如果目标类型 是引用类型,那么转换的效果与对于某个虚设变量 temp 进行声明和初始化 目标类型 temp(表达式 ); 然后以 temp 作为转换结果相同。
  • 否则,从表达式 直接初始化结果对象。
(C++17 起)
6) 否则,如果从表达式 到目标类型 的转换是某个标准转换序列的逆转,并且该转换序列不含以下任何转换,那么可以通过 static_cast 进行该逆转:
(C++17 起)
如果程序使用 static_cast 进行了某个非法标准转换序列的逆转,那么它非良构。
7) 否则,对表达式 应用左值到右值、数组到指针和函数到指针转换。在这些转换后,只有以下转换能通过 static_cast 进行:
a) 有作用域枚举类型的值可以转换到整数或浮点类型。
  • 如果目标类型 是(可有 cv 限定的)bool,那么结果在表达式 的原值为零时是 false,在其他情况下是 true
  • 如果目标类型 是(可有 cv 限定的)bool 以外的整数类型,那么在表达式 的原值能够以目标类型 表示时不会改变值。否则结果值未定义。
(C++20 前)
  • 如果目标类型 是整数类型,那么结果与先转换到枚举的底层类型再转换到目标类型 的结果相同。
(C++20 起)
  • 如果目标类型 是浮点类型,那么结果与将原值转换到目标类型 的结果相同。
(C++11 起)
b) 整数或枚举类型的值可以转换到任意完整的枚举类型
  • 如果目标类型 的底层类型固定,那么表达式 会先以整数提升整数转换转换到底层类型(如有必要),然后再转换到目标类型 。
  • 如果目标类型 的底层类型不固定,那么表达式 的值在原值处于枚举值的范围中时不会改变,否则行为未定义。
c) 浮点类型的值可以转换到任意完整的枚举类型。结果与将表达式 的原值先转换目标类型 的底层类型,然后再转换到目标类型 本身的结果相同。
d) 浮点类型的纯右值可以转换到其他任意浮点类型。
  • 如果表达式 的源值能以目标类型准确表示,那么就不会更改它。
  • 如果表达式 的源值处于目标类型的两个可表示值之间,那么由实现定义结果是这两个值中的哪一个。[2]
  • 否则,行为未定义。
(C++23 起)
e) “指向 cv1 Base 的指针”类型的右值 (C++11 前)纯右值 (C++11 起)在满足以下所有条件时可以显式转换到“指向 cv2 Derived 的指针”:
  • Derived 是完整类类型。
  • BaseDerived 的基类
  • cv1 是不多于 cv2 的 cv 限定。
如果表达式 是空指针值,那么结果是目标类型 的空指针值。否则,结果是指向表达式 指向的 Base 类型对象的外围 Derived 类型对象的指针。
如果满足以下任意条件,那么程序非良构:
  • BaseDerived虚基类
  • BaseDerived 的某个虚基类的基类。
  • 不存在从“指向 Derived 的指针”到“指向 Base 的指针”的合法标准转换
如果表达式 既不是空指针值,实际上也不指向某个 Derived 类型对象的基类子对象,那么行为未定义。
f) “指向 Derivedcv1 T 类型成员的指针”类型的右值 (C++11 前)纯右值 (C++11 起)在满足以下所有条件时可以显式转换到“指向 Basecv2 T 类型成员的指针”:
  • Derived 是完整类类型。
  • BaseDerived 的基类
  • cv1 是不多于 cv2 的 cv 限定。
如果表达式 是空成员指针值,那么结果是目标类型 的空成员指针值。否则,结果是指向 Base 类的原(可能间接的)成员的指针。
如果不存在从“指向 BaseT 类型成员的指针”到“指向 DerivedT 类型成员的指针”的合法标准转换,那么程序非良构。
如果表达式 既不是空成员指针值,也不表示 Base 类的(可能间接的)成员,那么行为未定义。
g) “指向 cv1 void 的指针”类型的右值 (C++11 前)纯右值 (C++11 起)T 是对象类型且 cv1 是不多于 cv2 的 cv 限定时可以显式转换到“指向 cv2 T 的指针”类型。
  • 如果表达式 是空指针值,那么结果是目标类型 的空指针值。
  • 如果表达式 表示内存中某个字节的地址 A,并且 A 满足 T对齐要求,那么结果指针同样表示 A
  • 其他此类指针转换的结果未指定。
  • 如果表达式 是先前从“指向 cv3 T 的指针”类型对象转换得到的结果,那么本次转换的结果是原来的值。
(C++17 前)
  • 如果表达式 表示内存中某个字节的地址 A,但是 A 不满足 T对齐要求,那么结果未指定。
  • 否则,如果表达式 指向 a,并且存在 T 类型(忽略 cv 限定)对象 b 使得 ab 的指针可以互相转换(见下文),那么结果是指向 b 的指针。
  • 否则,该转换不会修改指针的值。
(C++17 起)

同所有转换表达式,结果是:

  • 左值,如果目标类型 是左值引用类型或到函数类型的右值引用类型 (C++11 起)
  • 亡值,如果目标类型 是到对象类型的右值引用类型;
(C++11 起)
  • 否则是纯右值。
  1. 这种 static_cast 用于在 std::move 中实现移动语义。
  2. 如果支持 IEEE,那么舍入默认为到最接近。

指针可以互相转换的对象

满足以下条件时,两个对象 ab指针可以互相转换

  • 它们是同一个对象,或
  • 一个是联合体对象而另一个是该对象的非静态数据成员,或
  • 一个是标准布局的类对象,而另一个是该对象的首个非静态数据成员,或是该对象的任何基类子对象,或
  • 存在对象 c 使得 ac 的指针可以互相转换,而 cb 的指针可以互相转换。
union U { int a; double b; } u;
void* x = &u;                        // x 的值是“指向 u 的指针”
double* y = static_cast<double*>(x); // y 的值是“指向 u.b 的指针”
char* z = static_cast<char*>(x);     // z 的值是“指向 u 的指针”

注解

使用 static_cast 进行的基类到派生类转换(向下转换)不会在运行时检查该对象的type#动态类型确实为 D,因此它只能在该前提条件通过其他方法保证时,例如在实现静态多态时,才能安全使用。安全的向下转换可以用 dynamic_cast 执行。

static_cast 也能用来通过进行到指定类型的函数到指针转换来消解函数重载的歧义,如

std::for_each(files.begin(), files.end(),
              static_cast<std::ostream&(*)(std::ostream&)>(std::flush));

关键词

static_cast

示例

#include <iostream>
#include <vector>
 
struct B
{
    int m = 42;
    const char* hello() const
    {
        return "Hello world,这里是 B!\n";
    }
};
 
struct D : B
{
    const char* hello() const
    {
        return "Hello world,这里是 D!\n";
    }
};
 
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
 
int main()
{
    // 1. 静态向下转换
    D d;
    B& br = d; // 通过隐式转换向上转换
    std::cout << "1) " << br.hello();
    D& another_d = static_cast<D&>(br); // 向下转换
    std::cout << "1) " << another_d.hello();
 
    // 3. 左值到亡值
    std::vector<int> v0{1, 2, 3};
    std::vector<int> v2 = static_cast<std::vector<int>&&>(v0);
    std::cout << "3) 移动后,v0.size() = " << v0.size() << '\n';
 
    // 4. 弃值表达式
    static_cast<void>(v2.size());
 
    // 5. 初始化转换
    int n = static_cast<int>(3.14); 
    std::cout << "5) n = " << n << '\n';
    std::vector<int> v = static_cast<std::vector<int>>(10);
    std::cout << "5) v.size() = " << v.size() << '\n';
 
    // 6. 隐式转换的逆转换
    void* nv = &n;
    int* ni = static_cast<int*>(nv);
    std::cout << "6) *ni = " << *ni << '\n';
 
    // 7a. 有作用域枚举到 int 或 float
    E e = E::TWO;
    int two = static_cast<int>(e);
    std::cout << "7a) " << two << '\n';
 
    // 7b. int 到枚举,枚举到另一枚举
    E e2 = static_cast<E>(two);
    [[maybe_unused]]
    EU eu = static_cast<EU>(e2);
 
    // 7f. 指向成员指针向上转换
    int D::*pm = &D::m;
    std::cout << "7f) " << br.*static_cast<int B::*>(pm) << '\n';
 
    // 7g. void* 到任意对象指针
    void* voidp = &e;
    [[maybe_unused]]
    std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}

输出:

1) Hello world,这里是 B!
1) Hello world,这里是 D!
3) 移动后,v0.size() = 0
5) n = 3
5) v.size() = 10
6) *ni = 3
7a) 2
7f) 42

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 137 C++98 指向 void 的指针的常量性或易变性可以被去除 不能通过 static_cast 去除
CWG 427 C++98 向下转换与直接初始化之间有歧义 此时选择向下转换
CWG 439 C++98 当指向对象的指针先转换到指向 void 的指针再转换回自身时,
它的值只有在目标类型有相同的 cv 限定时才会保留
允许 cv 限定不同
CWG 1094 C++98 未指明浮点数到枚举的转换 指明转换规则
CWG 1320 C++11 未指明有作用域枚举到 bool 的转换 指明转换规则
CWG 1412 C++98 从“指向 void 的指针”转换到“指向对象的指针”的结果不明确 使之明确
CWG 1447 C++11 未指明位域到右值引用的转换(无法绑定引用到位域) 指明转换规则
CWG 1766 C++98 整数或枚举到(另一)枚举的转换的结果在表达式 超出(结果枚举的)范围时未指定 此时行为未定义
CWG 1832 C++98 整数或枚举到(另一)枚举的转换允许目标类型 不完整 此时结果枚举类型必须完整
CWG 2224 C++98 从具有基类类型的成员到它的具有派生类类型的完整对象的转换合法 此时行为未定义
CWG 2254 C++11 无数据成员的标准布局类对象可指针间转换到它的首个基类 可指针间转换到它的任意基类
CWG 2284 C++11 联合体对象和该对象的非静态数据成员的指针不可互相转换 可以互相转换
CWG 2310 C++98 基类到派生类的指针转换和派生类到基类的成员指针转换不需要派生类是完整类型 必须是完整类型
CWG 2338 C++11 到底层类型固定的枚举类型的转换在表达式 超出范围时的行为未定义 先转换到底层类型
(不会有未定义行为)
CWG 2499 C++11 标准布局类可以有非指针可互转换的基类,即使所有基类子对象均拥有相同地址 它没有
CWG 2718 C++98 基类到派生类的引用转换不需要派生类是完整类型 必须是完整类型
CWG 2882 C++98 不明确 static_cast<void>(expr) 是否
会尝试组成从 exprvoid 的隐式转换序列
此时不会尝试

引用

  • C++23 标准(ISO/IEC 14882:2024):
  • 7.6.1.9 Static cast [expr.static.cast]
  • C++20 标准(ISO/IEC 14882:2020):
  • 7.6.1.8 Static cast [expr.static.cast]
  • C++17 标准(ISO/IEC 14882:2017):
  • 8.2.9 Static cast [expr.static.cast]
  • C++14 标准(ISO/IEC 14882:2014):
  • 5.2.9 Static cast [expr.static.cast]
  • C++11 标准(ISO/IEC 14882:2011):
  • 5.2.9 Static cast [expr.static.cast]
  • C++98 标准(ISO/IEC 14882:1998):
  • 5.2.9 Static cast [expr.static.cast]
  • C++03 标准(ISO/IEC 14882:2003):
  • 5.2.9 Static cast [expr.static.cast]

参阅