C++ 类的非静态数据成员默认初始化

144 阅读2分钟

C++11 之前使用默认初始化

C++11 之前对非静态数据成员初始化需要用到初始化列表。

有个问题是,如果类的数据成员比较多,我们又需要定制一些数据成员的初始化操作的时候,需要写很多的构造函数。

来看一个例子:

#include <iostream>
#include <string>class X {
public:
    X() : a_(0), b_(0.), c_("hello world") {}
    X(int a) : a_(a), b_(0.), c_("hello world") {}
    X(double b) : a_(0), b_(b), c_("hello world") {}
    X(const std::string c) : a_(0), b_(0.), c_(c) {}
    
    void PrintX() {
        std::cout << "----------------------" << std::endl;
        std::cout << "a_: " << this->a_ << std::endl;
        std::cout << "b_: " << this->b_ << std::endl;
        std::cout << "c_: " << this->c_ << std::endl;
    }
​
private:
    int a_;
    double b_;
    std::string c_;
};
​
int main() {
    X* x_1 = new X();
    x_1->PrintX();
​
    X* x_2 = new X(1);
    x_2->PrintX();
​
    X* x_3 = new X(2.0);
    x_3->PrintX();
​
    X* x_4 = new X("C++");
    x_4->PrintX();
}

因为有时候要给不同的成员变量进行初始化,所以要写好几个不同版本的构造函数,并且可以发现构造函数里有很多冗余代码。

运行结果:

linrongjian@hhb-ssd:/data/modern_cpp$ g++ -std=c++11 nonstatic_datamemeber_init.cpp -o nonstatic_datamemeber_init
linrongjian@hhb-ssd:/data/modern_cpp$ ./nonstatic_datamemeber_init
----------------------
a_: 0
b_: 0
c_: hello world
----------------------
a_: 1
b_: 0
c_: hello world
----------------------
a_: 0
b_: 2
c_: hello world
----------------------
a_: 0
b_: 0
c_: C++

这种方式在数据成员变多的时候,需要频繁地更改和增加构造函数,维护起来非常的麻烦。

C++11 的默认初始化方式

C++11 允许在声明非静态数据成员的时候同时用 =(声明时默认初始化) 和 {} (列表初始化)。

还是刚才的例子,可以这样写:

#include <iostream>
#include <string>class X {
public:
    X() {}
    X(int a) : a_(a) {}
    X(double b) : b_(b) {}
    X(const std::string c) : c_(c) {}
    
    void PrintX() {
        std::cout << "----------------------" << std::endl;
        std::cout << "a_: " << this->a_ << std::endl;
        std::cout << "b_: " << this->b_ << std::endl;
        std::cout << "c_: " << this->c_ << std::endl;
    }
​
private:
    // 这里也可以用列表初始化 {}
    int a_ = 0;
    double b_ = 0.;
    std::string c_ = "hello world";
};
​
int main() {
    X* x_1 = new X();
    x_1->PrintX();
​
    X* x_2 = new X(1);
    x_2->PrintX();
​
    X* x_3 = new X(2.0);
    x_3->PrintX();
​
    X* x_4 = new X("C++");
    x_4->PrintX();
}

可以发现在声明数据成员的时候直接用 = 初始化(也可以 {} 列表初始化)和构造函数的列表初始化同时使用了。

同时构造函数少了很多冗余代码,每个构造函数只需要专注于特殊成员的初始化(而不是写出全部数据成员的初始化),对于没写出来的数据成员,默认使用声明时初始化的值。

显然这种方式增强了代码可维护性。

运行效果和上面是等效的:

linrongjian@hhb-ssd:/data/modern_cpp$ g++ -std=c++11 nonstatic_datamemeber_init.cpp -o nonstatic_datamemeber_init
linrongjian@hhb-ssd:/data/modern_cpp$ ./nonstatic_datamemeber_init
----------------------
a_: 0
b_: 0
c_: hello world
----------------------
a_: 1
b_: 0
c_: hello world
----------------------
a_: 0
b_: 2
c_: hello world
----------------------
a_: 0
b_: 0
c_: C++

位域默认初始化(C++20)

有些变量在存储时不需要一个完整的字节,而只需要占用几个位。

为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。

C++20 允许对数据成员的位域进行默认初始化。

例:

#include <iostream>class S {
public:
    int x : 8 = 11;
    int y : 4{7};
};
​
int main() {
    S* s = new S();
    std::cout << s->x << ' ' << s->y << std::endl;
}

这里低 8 位被初始化为 11,高 4 位被初始化为 7

linrongjian@hhb-ssd:/data/modern_cpp$ g++ -std=c++2a bitfield_init.cpp -o bitfield_init
linrongjian@hhb-ssd:/data/modern_cpp$ ./bitfield_init
11 7

参考资料

  1. 现代C++语言核心特性解析