11.4 Class members

member-specification:

    member-declaration member-specificationopt

    access-specifier : member-specificationopt

member-declaration:
    attribute-specifier-seqopt
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :
    access-specifier :

member-declarator-list:

member-declarator:

virt-specifier-seq:

virt-specifier:
pure-specifier:

11.4.3 Special member functions

1

默认构造函数([class.default.ctor])、拷贝构造函数、移动构造函数([class.copy.ctor])、拷贝赋值运算符、移动赋值运算符([class.copy.assign]),以及预期析构函数([class.dtor]),统称为特殊成员函数(special member functions)。

【注释:当程序未显式声明这些成员函数时,实现会为某些类类型隐式地声明它们。如果这些函数被odr-used([basic.def.odr]),或在常量求值([expr.const])中需要,实现将隐式定义它们。 ——注释结束】

隐式声明的特殊成员函数在class-specifier的}处(类定义结束)被声明。程序不得显式定义这些隐式声明的特殊成员函数。

Paw5zx个人理解:点击查看更多

程序不得显式定义这些隐式声明的特殊成员函数。

如果某个特殊成员函数已经被编译器隐式声明,则用户不可以手动定义这些函数

1
2
3
4
5
6
7
struct A
{
// 没有显式声明构造函数或析构函数
}; // <- 此处,编译器隐式声明了 A::A() 和 A::~A()

// 试图定义这个隐式声明的默认构造函数
A::A() { std::cout << "user-defined"; }
6

可用的(eligible)特殊成员函数是满足以下条件的特殊成员函数:

  • (6.1) 未被定义为delete

  • (6.2) 其关联的约束条件(若存在)([temp.constr])已被满足,并且;

  • (6.3) 不存在约束更强([temp.constr.order])的同类(same kind)特殊成员函数。

Paw5zx个人理解:点击查看更多

(6.2) (6.3)都是对模板类的约束。对普通非模板类而言,可用的特殊成员函数等价于非delete的特殊成员函数。

11.4.4 Constructors

11.4.4.1 Default constructors

3

若一个默认构造函数不是用户提供的,并且同时满足以下条件,则其是平凡的:

  • (3.1) 其所属的类没有虚函数([class.virtual])且没有虚基类([class.mi]);
  • (3.2) 其所属类的任意非静态数据成员都没有默认成员初始化器([class.mem]);
  • (3.3) 其所属类的所有直接基类的默认构造函数都是平凡的;
  • (3.4) 对于其所属类的每个类类型(或类类型数组)非静态数据成员,该成员的类型要具有平凡的默认构造函数。

否则默认构造函数是不平凡的。

11.4.4.2 Copy/move constructors

11

若类X的拷贝/移动构造函数不是用户提供的,并且同时满足以下条件,则其是平凡的:

  • (11.1) 类X没有虚函数([class.virtual])且没有虚基类([class.mi]);并且
  • (11.2) 用于拷贝/移动每个直接基类子对象的构造函数都是平凡的;并且
  • (11.3) 对于X的每个类类型(或类类型数组)非静态数据成员,用于拷贝、移动该成员的构造函数都是平凡的;

否则拷贝/移动构造函数是不平凡的。

11.4.9 Bit-fields

1
使用以下形式的成员声明符,指定一个位域:     identifieropt attribute-specifier-seqopt : constant-expression brace-or-equal-initializeropt
可选的属性说明序列(`attribute-specifier-seq`)作用于被声明的实体。位域不能是静态成员。位域必须是整型或枚举类型;位域的语义属性不属于类成员类型的组成部分。常量表达式(constant-expression)应为一个大于等于零的整型常量表达式,称为位域的宽度。如果位域的宽度大于其类型(或,对于枚举类型,宽度大于其底层类型)的宽度,超出的比特是填充位(参见[[basic.types]]())。类对象内位域的分配规则由实现定义。位域的对齐规则由实现定义。位域会被打包到某个可寻址的分配单元中。
【注:在某些机器上,位域可能跨越多个分配单元,而在另一些机器上则不会。在某些机器上,位域自右向左分配,而在另一些机器上,自左向右分配。——注释结束】
2
位域的声明若省略identifier,就声明了一个未命名位域。未命名位域不是类成员,也不能被初始化。未命名位域不得使用带有cv限定符的类型进行声明。
【注:未命名位域常用于填充,以符合外部强加的布局要求。——注释结束】
作为一种特殊情况,若未命名位域的宽度为0,则表示要求下一个位域必须在分配单元的边界上开始对齐。只有在声明未命名位域时,宽度才可以为0。
Paw5zx个人理解:点击查看更多

分配单元是根据位域声明的底层类型而定的,对于上述未命名位域,有以下示例:

1
2
3
4
5
6
7
8
struct S 
{
unsigned int a : 3; // 从字节0开始,占0-2bit
unsigned int : 0; // 强制到unsigned int边界,即下个位域从第4字节开始
unsigned long b : 5; // b的底层类型为unsigned long,从字节4开始,占0-4bit
unsigned long : 0; // 强制到unsigned long边界,即下个位域从第8字节开始
unsigned long long c : 6;
};
3
取址运算符`&`不得用于位域,因此不存在指向位域的指针。非常量引用不得绑定到位域([[dcl.init.ref]]())。
【注:如果`const T&`类型的引用的初始化器是一个指向位域的左值,则该引用会绑定到一个用该位域的值初始化的临时对象上;而不是直接绑定到该位域。参见[[dcl.init.ref]]()。
Paw5zx个人理解:点击查看更多

位域只占用某几个bit,所以不能按字节寻址,所以无法取地址。因此编译器会生成一个临时对象,把位域的值拷贝进去,然后让const T&引用这个临时对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct S
{
unsigned int a : 3;
};

int main()
{
S s{9}; // 二进制1001,只能存低3位,溢出了
const unsigned int& r = s.a;
std::cout << r << std::endl; // 输出1
s.a = 3; // 改变位域的值
std::cout << r << std::endl; // 输出的还是1,因为const T&绑定到位域时会生成临时量,而临时量没有被改变
}
4
如果一个整型值(bool除外)被存储到宽度为`N`的位域中,且该值可以在一个假设的、宽度为`N`且符号性与该位域类型相同的有符号或无符号整数类型中可表示,那么原始值与位域中的值在比较时相等。
如果将`true`或`false`存储到一个任意大小的类型为bool的位域中(包括1位的位域),那么原始的布尔值与位域中的值在比较时相等。
如果一个枚举类型的值被存储到相同类型的位域中,且该位域的宽度足以容纳该枚举类型的所有值([[dcl.enum]]()),那么原始值与位域中的值在比较时相等。
【示例:
1
2
3
4
5
6
7
8
9
10
enum BOOL { FALSE=0, TRUE=1 };
struct A {
BOOL b:1;
};
A a;
void f() {
a.b = TRUE;
if (a.b == TRUE) // yields true
{ /* ... */ }
}
——示例结束】
Paw5zx个人理解:点击查看更多

整形:存到位域中,超出位域宽度的部分被截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct S
{
unsigned int bf : 3; // 3位无符号位域,范围 0~7
};

int main(){
S s;
s.bf = 5; // 5在范围内,可表示
std::cout << std::boolalpha << (s.bf == 5) << std::endl; // 输出true

s.bf = 8; // 8超出范围,发生截断
std::cout << std::boolalpha << (s.bf == 8) << std::endl; // 输出false
std::cout << std::boolalpha << (s.bf == 0) << std::endl; // 输出true,因为8的二进制1000,截断后只剩低3位000,即0
}

bool型:无论bool位域的大小是多少,存储true或false时,比较结果与原值相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct S
{
bool b1 : 1;
bool b2 : 3;
};

int main()
{
S s;
s.b1 = true;
s.b2 = false;
std::cout << std::boolalpha
<< (s.b1 == true) << " " // true
<< (s.b2 == false) << std::endl; // true
}

枚举类型:如果枚举值存入相同枚举类型的位域,且位域宽度足够容纳该枚举的所有可能值,那么存储与比较结果相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Color的底层是unsigned int
enum Color { Red=0, Green=1, Blue=2 };

struct S
{
Color c : 2; // 2位足够表示0,1,2三个值
};

int main()
{
S s;
s.c = Green;
std::cout << std::boolalpha << (s.c == Green) << std::endl; // 输出true
s.c = Blue;
std::cout << std::boolalpha << (s.c == Blue) << std::endl; // 输出true

// std::cout << std::boolalpha << (s.c == Red) << std::endl; // 将Blue的值改为4后,此句输出true,因为4超出2位可表示的最大unsigned int值,被截断,截断后是1
}