Modern C++特性:聚合初始化

有一种初始化C++对象的方法是聚合初始化,它能初始化被大括号包括起来的多个值:

  1. struct Data {

  2. std::string name;

  3. double value;

  4. };

  5. Data x{"test1", 6.778};

从C++17开始,聚合还可以有基类,因此可以初始化从其他类/结构体派生而来的结构体:

  1. struct MoreData : Data {

  2. bool done;

  3. };

  4. MoreData y{{"test1", 6.778}, false};

可以看到,聚合初始化现在支持以嵌套括号传递初始值给基类的成员变量。也可以省略嵌套括号:

  1. MoreData y{"test1", 6.778, false};

一般要初始化嵌套聚合的规则是,将初始值按照基类/基结构体和成员的声明顺序进行传递。

注:所谓聚合,可以是数组,或简单的类C的类(即没有用户定义的构造函数,没有private和protected的非静态成员变量,没有虚函数,在C++17之前还要求不能有基类)。

扩展聚合初始化的动机

没有此特性的话,从一个禁止聚合初始化的类型派生结构体,需要定义一个构造函数:

  1. struct Cpp14Data : Data {

  2. bool done;

  3. Cpp14Data (const std::string& s, double d, bool b)

  4. : Data{s,d}, done{b} {

  5. }

  6. };

  7. Cpp14Data y{"test1", 6.778, false};

现在,我们可以自由使用嵌套括号的语法,如果只传一个值时,可以省略括号:

  1. MoreData x{{"test1", 6.778}, false}; // C++17正确

  2. MoreData y{"test1", 6.778, false}; // 正确

注意,这是因为它现在是个聚合了,其他初始化也可以:

  1. MoreData u; // value和done没有初始化

  2. MoreData z{}; // 可以,value和done初始化为0和false

这样不初始化很危险,可以使用缺省成员变量初始化:

  1. struct Data {

  2. std::string name;

  3. double value{0.0};

  4. };

  5. struct Cpp14Data : Data {

  6. bool done{false};

  7. };

或者仍然可以提供一个构造函数。

使用扩展聚合初始化

一个典型的应用是通过给基类添加额外的成员变量或操作符来初始化C风格的结构体,例如:

  1. struct Data {

  2. const char* name;

  3. double value;

  4. };

  5. struct CppData : Data {

  6. bool critical;

  7. void print() const {

  8. std::cout << '[' << name << ',' << value << "]\n";

  9. }

  10. };

  11. CppData y{{"test1", 6.778}, false};

  12. y.print();

这里,内括号中的实参会传递给基类 Data
注意,可以跳过初始值。那样元素会被初始化为0值(调用缺省构造函数或用0falsenullptr初始化基本数据类型)。例如:

  1. CppData x1{}; // 以0初始化所有元素

  2. CppData x2{{"msg"}}; // 等同于 {{"msg",0.0},false}

  3. CppData x3{{}, true}; // 等同于 {{nullptr,0.0},true}

  4. CppData x4; // 基本类型的值未指定

注意使用空大括号和不使用括号的区别:

  • x10初始化所有成员,因此 string name是缺省构造的, double value是以 0.0初始化, bool flag以 false初始化。

  • d通过构造函数只初始化了 string name,所有其他成员变量都没有初始化,因此它们的值未定义。

可以从一个非聚合的类派生出聚合类。例如:

  1. struct MyString : std::string {

  2. void print() const {

  3. if (empty()) {

  4. std::cout << "<undefined>\n";

  5. }

  6. else {

  7. std::cout << c_str() << '\n';

  8. }

  9. }

  10. };

  11. MyString x{{"hello"}};

  12. MyString y{"world"};

要注意这里并不是通常的用于多态的public继承。因为 std::string没有虚成员函数,要非常小心,不要搞混了两个类型。
甚至可以从多个基类和/或聚合派生出聚合:

  1. template<typename T>

  2. struct D : std::string, std::complex<T>

  3. {

  4. std::string data;

  5. };

然后按以下方式使用和初始化:

  1. D<float> s{{"hello"}, {4.5,6.7}, "world"}; // C++17起正确

  2. D<float> t{"hello", {4.5, 6.7}, "world"}; // C++17起正确

  3. std::cout << s.data; // 输出: ”world”

  4. std::cout << static_cast<std::string>(s); // 输出: ”hello”

  5. std::cout << static_cast<std::complex<float>>(s); // 输出: (4.5,6.7)

内部的初始化列表按基类的成员变量声明的顺序传到基类。

聚合的定义

总结一下,从C++17开始,聚合定义如下:

  • 可以是一个数组

  • 或是一自定义类型(类、结构体、或联合),但需要:

    • 没有用户定义或显式的构造函数

    • 没有通过using声明继承而来的构造函数

    • 没有privateprotected的非静态成员变量

    • 没有虚函数

    • 没有virtualprivate或“protected*的基类

要能使用聚合,它还必须在初始化过程中没有private或“protected*的基类成员或构造函数。
C++17引入了新的类型粹取 is_aggregate<>用于测试某个类型是否是聚合:

  1. template<typename T>

  2. struct D : std::string, std::complex<T> {

  3. std::string data;

  4. };

  5. D<float> s{{"hello"}, {4.5,6.7}, "world"}; // C++17开始正确

  6. std::cout << std::is_aggregate<decltype(s)>::value; // 输出: 1 (true)

向后兼容

注意,以下代码将编译不过:
lang/aggr14.cpp

  1. struct Derived;

  2. struct Base {

  3. friend struct Derived;

  4. private:

  5. Base() {

  6. }

  7. };

  8. struct Derived : Base {

  9. };

  10. int main()

  11. {

  12. Derived d1{}; // C++17后不支持

  13. Derived d2; // 仍然正常,但可能不能初始化

  14. }

C++17以前, Derived不是个聚合,所以

  1. Derived d1{};

将调用隐含定义的 Derived的缺省构造函数,它会再调用基类 Base的缺省构造函数。尽管基类的构造函数是private的,但它可以被派生类的缺省构造函数调用,因为派生类被定义为friend类了。
C++17开始,示例代码中的 Derived是一个聚合,不再有隐含的缺省构造函数(不再通过using声明继承构造函数)。所以通过聚合初始化进行初始化,但它不能调用private的基类构造函数。是不是基类的friend都没用。

  • 22
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值