在c++中初始化对象的一种方法是聚合初始化,它允许使用花括号从多个值初始化:
struct Data
{
std::string name;
double value;
};
Data x{"test1", 6.778};
在c++ 17中聚合可以有基类,所以对于从其他类/结构派生的结构,允许初始化列表:
struct MoreData : Data
{
bool done;
};
MoreData y{{"test1", 6.778}, false};
如您所见,聚合初始化现在支持嵌套大括号,以便将值传递给基类的派生成员。
1. 扩展聚合初始化的意图
如果没有这个特性,则从另一个禁用的聚合初始化派生结构,因此必须定义一个构造函数:
struct Cpp14Data : Data
{
bool done;
Cpp14Data (const std::string& s, double d, bool b)
Data{s,d}, done{b}
{
}
};
Cpp14Data y{"test1", 6.778, false};
C++17提供了这个功能,语法略有不同,它很方便的将值传递到基类,如下:
MoreData y{{"test1", 6.778}, false};
2. 使用扩展聚合初始化
一个典型的应用是子类能够使用列表初始化父类的C风格成员变量,子类可以添加额外的数据成员或操作。例如:
struct Data
{
const char* name;
double value;
};
struct PData : Data
{
bool critical;
void print() const
{
std::cout << '[' << name << ',' << value << "]\n";
}
};
PData y{{"test1", 6.778}, false};
y.print();
这里,内嵌括号中的{"test1", 6.778}参数被传递给基类成员变量。
注意,您可以跳过初始值。在这种情况下,元素初始化为零(调用默认构造函数或使用0、false或nullptr初始化基本数据类型)。
例如:
PData a{}; // zero-initialize all elements
PData b{{"msg"}}; // 等同于 {{"msg",0.0},false}
PData c{{}, true}; // 等同于 {{nullptr,0.0},true}
PData d; //基本类型的值是未指定的
注意在这里使用空花括号和完全不使用花括号的区别:
A. a zero-initializes all members,C风格字符串默认地为空指针(nullptr),double值初始化为0.0,bool标志初始化为false。
B. 变量d的定义所有其他成员都没有初始化,并且具有未指定的值。
在visual studio 2019上测试结果如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9MGRPVTRUFGeW1mYoBnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2YTOwETMwQTM5IDOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
在这里我们把变量Data中的name类型修改为std::string,代码如下:
例1:
#include <iostream>
#include <string>
struct Data
{
std::string name;
double value;
};
struct PData : Data
{
bool critical;
void print() const
{
std::cout << '[' << name << ',' << value << "]\n";
}
};
int main()
{
PData a{}; // zero-initialize all elements
PData b{ {"msg"} }; // same as {{"msg",0.0},false}
PData c{ {}, true }; // same as {{nullptr,0.0},true}
PData d; // values of fundamental types are unspecified
return 0;
}
此时变量a,b都会调用默认构造函数进行初始化。
结果如下:
还可以从非聚合类派生聚合。例如:
struct MyString : std::string
{
void print() const
{
if (empty())
{
std::cout << "<undefined>\n";
}
else
{
std::cout << c_str() << '\n';
}
}
};
MyString y{{"test1"}};
甚至可以从多个基类和/或聚合派生聚合:
template<typename T>
struct D : std::string, std::complex<T>
{
std::string data;
};
然后你可以使用和初始化如下:
D<float> s{{"hello"}, {4.5,6.7}, "world"}; // OK since C++17
std::cout << s.data; // outputs: ”world”
std::cout << static_cast<std::string>(s); // outputs: ”hello”
std::cout << static_cast<std::complex<float>>(s); // outputs: (4.5,6.7)
在这里内部初始化器列表按照基类声明的顺序传递给基类。这个新特性还有助于用很少的代码定义重载lambdas(后续的文章会介绍)。
详细代码如下:
例2:
#include <iostream>
#include <string>
#include <complex>
template<typename T>
struct D : std::string, std::complex<T>
{
std::string data;
};
int main(void)
{
D<float> s{ {"hello"}, {4.5,6.7}, "world" }; // OK since C++17
std::cout << s.data << std::endl; // outputs: ”world”
std::cout << static_cast<std::string>(s) << std::endl;; // outputs: ”hello”
std::cout << static_cast<std::complex<float>>(s) << std::endl; // outputs: (4.5,6.7)
return 0;
}
结果如下:
3. 聚合的定义
总而言之,由于c++ 17将聚合定义为:
a. 数组;
b. 或类类型(类、结构或联合):
-- 没有用户定义的构造上述;
--没有通过using声明的继承构造函数;
--没有private或者protect的non-static数据成员;
--没有virtual函数;
--没有virtual或者private或者protected的基类。
为了能够使用聚合,还需要在初始化期间不使用私有或受保护基类的成员或构造函数。
c++ 17还引入了一个新的类型trait is_aggregate<>来测试一个类型是否是一个聚合:
template<typename T>
struct D : std::string, std::complex<T>
{
std::string data;
};
D<float> s{{"hello"}, {4.5,6.7}, "world"}; // OK since C++17
std::cout << std::is_aggregate<decltype(s)>::value; // outputs: 1 (true)
例 3:
#include <iostream>
#include <string>
#include <complex>
template<typename T>
struct D : std::string, std::complex<T>
{
std::string data;
};
int main(void)
{
D<float> s{ {"hello"}, {4.5,6.7}, "world" }; // OK since C++17
const auto result = std::is_aggregate<decltype(s)>::value;
std::cout << result << std::endl;
return 0;
}
结果如下:
4. 向前不兼容
注意下面的例子中不会兼容c++14的语法。
例4:
#include<iostream>
struct Derived;
struct Base
{
friend struct Derived;
private:
Base()
{
std::cout << "Base constructor." << std::endl;
}
};
struct Derived : Base
{
};
int main()
{
Derived d1{}; // ERROR since C++17
Derived d2; // still OK (but might not initialize)
return 0;
}
C++17编译会报如下错误:
C++14编译输出结果如下;
Base constructor.
Base constructor.
在c++ 17之前,Derived不是一个聚合。因此,Derived d1 {}调用派生类的编译期自动生成的默认构造函数,该构造函数在默认情况下调用基类基的默认构造函数。虽然基类的默认构造函数是私有的,但是可以通过派生类的默认构造函数调用它,因为派生类被定义为friend类。
自从c++ 17起,Derived在这个示例中是一个聚合类,根本没有一个隐式默认构造函数(没有通过using声明的继承构造函数)。因此,初始化是一个聚合初始化,它不允许调用基类的私有构造函数。是否是基类的friend类并不重要。