专业编程基础技术教程

网站首页 > 基础教程 正文

C++ 20 新特性(2):支持位段类型的类成员的直接初始化

ccvgpt 2024-10-10 05:03:05 基础教程 12 ℃

支持位段类型的类成员的直接初始化

C++ 20 增加了支持位段类型的类成员的初始化,例如下面的代码:

#include <iostream>

class CA
{
public:
    int m_a1 { 2 };           // (1)C++11开始支持,大括号方式的普通变量初始化
    int m_a2 = 4;            // (2)C++11开始支持,等号方式的普通变量初始化
    int m_b1 : 5 { 6 };      // (3)C++20开始支持,大括号方式的位段变量初始化
    int m_b2 : 11 = 8;     // (4)C++20开始支持,等号方式的位段变量初始化
};

int main( int argc, char * argv[] )
{
    CA a1;
    std::cout << a1.m_a1 << " " << a1.m_a2 << std::endl;
    std::cout << a1.m_b1 << " " << a1.m_b2 << std::endl;
    return 0;
}

使用GCC编译时,指定不同的C++标准时,编译的结果也不一样:

C++ 20 新特性(2):支持位段类型的类成员的直接初始化

[foo@test code]$ g++ -std=c++98 a2.cpp
a2.cpp:6:15: warning: non-static data member initializers only available with ‘-std=c++11’ or ‘-std=gnu++11’
    6 |  int m_a1 { 2 };
      |               ^
a2.cpp:7:13: warning: non-static data member initializers only available with ‘-std=c++11’ or ‘-std=gnu++11’
    7 |  int m_a2 = 4;
      |             ^
a2.cpp:8:13: error: expected ‘;’ at end of member declaration
    8 |  int m_b1 : 5 { 6 };
      |             ^
      |              ;
a2.cpp:8:15: error: expected unqualified-id before ‘{’ token
    8 |  int m_b1 : 5 { 6 };
      |               ^
a2.cpp:9:18: error: an assignment cannot appear in a constant-expression
    9 |  int m_b2 : 11 = 8;
      |                  ^
a2.cpp:6:11: warning: extended initializer lists only available with ‘-std=c++11’ or ‘-std=gnu++11’
    6 |  int m_a1 { 2 };
      |           ^
a2.cpp:6:15: warning: extended initializer lists only available with ‘-std=c++11’ or ‘-std=gnu++11’
    6 |  int m_a1 { 2 };
      |               ^
[foo@test code]$ g++ -std=c++11 a2.cpp         
a2.cpp:8:15: warning: default member initializers for bit-fields only available with ‘-std=c++2a’ or ‘-std=gnu++2a’
    8 |  int m_b1 : 5 { 6 };
      |               ^
a2.cpp:9:16: warning: default member initializers for bit-fields only available with ‘-std=c++2a’ or ‘-std=gnu++2a’
    9 |  int m_b2 : 11 = 8;
      |                ^
[foo@test code]$ ./a.out
2 4
6 8
[foo@test code]$ g++ -std=c++20 a2.cpp
[foo@test code]$ ./a.out
2 4
6 8

类成员变量初始化的延伸分析

在定义类的局部变量或者使用 new 动态创建类实例时,会调用类的构造函数,如果没有显式定义类的构造函数,会使用类的默认构造函数。在类的默认构造函数中,对于简单类型的成员变量,默认是不会初始化的,也就是它的值是不确定的,这种不确定性是潜在问题的根源。因此对这种简单类型的类成员进行显式初始化是一个良好的编程习惯。

早期的 C++ 需要单独编写构造函数来进行类成员的初始化,例如:

class CA2
{
public:
    CA2() : m_c1( 2 ), m_c2( 4 ) { }   // (1)使用构造函数进行初始化
    int m_c1;
    int m_c2;
};

这样的写法虽然直观,但有点冗长,因此在 C++ 11 标准里面,模仿局部变量初始化的方式,增加了类成员变量的直接初始化,支持使用大括号和等号的方式初始化:

class CA3
{
public:
    int m_c1 { 3 };
    int m_c2 = 6;
};

其实局部变量也可以使用圆括号进行初始化,但是圆括号进行初始化时,如果使用另一个变量进行初始化,变量又跟类型重名 ,就和定义一个函数雷同,产生二义性。因此局部变量不推荐使用圆括号来初始化,C++11 扩展支持类成员初始化时,就干脆不支持圆括号进行初始化了。

在这样的直接初始化基础上,还可以再定义CA3类的构造函数,如果构造函数中有成员变量的初始化,则以构造函数为准,如果未显式初始化,则沿用定义成员变量时的初始化,这样不同的构造函数可以有选择的只初始化部分成员变量,不需要每个构造函数都重复将所有成员变量都初始化。

C++ 11 中只支持普通类型的直接初始化,不支持位段的直接初始化,直到C++ 20 才支持

主要是因为定义位段的大小,和后面的等号初始化,容易产生二义性,所以需要较多的讨论,最后明确要求等号用于初始化,而不会跟前面的位段大小组成表达式。例如:

static int g1 = 3;
static const int g2 = 4;
class CA4
{
public:
    int m_c1 : (g1 = 6);   // (1)Error,赋值本身语法正确,但不是常量,而定义位段大小必须使用常量
    int m_c2 : (g2 = 6);   // (2)Error,常量不能赋值
    int m_c3 : g2 = 6;     //  (3)OK,位段大小g2是常量,最后的 = 6 表示初始化,不会当(2)来解析
    int m_c4 : true ? g2 : (g1 = 3);  // (4)OK,g1赋值语法上正确,虽然不是常量,但实际并不会用到,也不会执行
    int m_c5 : true ? g2 : g1 = 3;    // (5)OK,最后的 = 3 表示初始化,不会当(4)来解析
};

从上面例子可以看到,位段大小如果使用 “ ? : ” 运算符,和等号初始化,会产生二义性,现在标准强制要求必须按初始化来解析。不过实际位段一般都是直接的数值常量,不会使用很复杂的表达式的。

最近发表
标签列表