天天看点

C++17之聚合类扩展

在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上测试结果如下:

C++17之聚合类扩展

在这里我们把变量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都会调用默认构造函数进行初始化。

结果如下:

C++17之聚合类扩展

还可以从非聚合类派生聚合。例如:

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;
}
           

结果如下:

C++17之聚合类扩展
C++17之聚合类扩展

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;
}
           

结果如下:

C++17之聚合类扩展

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++17之聚合类扩展

C++14编译输出结果如下;

Base constructor.
Base constructor.
           

    在c++ 17之前,Derived不是一个聚合。因此,Derived d1 {}调用派生类的编译期自动生成的默认构造函数,该构造函数在默认情况下调用基类基的默认构造函数。虽然基类的默认构造函数是私有的,但是可以通过派生类的默认构造函数调用它,因为派生类被定义为friend类。

    自从c++ 17起,Derived在这个示例中是一个聚合类,根本没有一个隐式默认构造函数(没有通过using声明的继承构造函数)。因此,初始化是一个聚合初始化,它不允许调用基类的私有构造函数。是否是基类的friend类并不重要。

继续阅读