天天看點

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類并不重要。

繼續閱讀