首頁 > 軟體

C++聚合體初始化aggregate initialization詳細介紹

2023-02-05 14:01:17

聚合體初始化(aggregate initialization)

C++有很多初始化物件的方法。其中之一叫做 聚合體初始化(aggregate initialization) ,這是聚合體專有的一種初始化方法。

從C語言引入的初始化方式是用花括號括起來的一組值來初始化類:

struct Data {
    std::string name;
    double value;
};
Data x = {"test1", 6.778};

自從C++11起,你可以忽略等號:

Data x{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->"test1", 6.778};

自從C++17起,聚合體可以擁有基礎類別。也就是說像下面這種從其他類派生出的子類也可以使用這種初始化方法:

struct MoreData : Data {
    bool done;
}
MoreData y{{"test1", 6.778}, false};

如你所見,聚合體初始化時可以用一個子聚合體初始化來初始化類中來自基礎類別的成員。

另外,你甚至可以省略子聚合體初始化的花括號:

MoreData y{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->"test1", 6.778, false};

這樣寫將遵循巢狀聚合體初始化時的通用規則,你傳遞的實參被用來初始化哪一個成員取決於它們的順序。

擴充套件聚合體初始化的動機

如果沒有這個特性,那麼所有的派生類都不能使用聚合體初始化,這意味著你要像下面這樣定義建構函式:

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

現在我們不再需要定義任何建構函式就可以做到這一點。

我們可以直接使用巢狀花括號的語法來實現初始化,

如果給出了內層初始化需要的所有值就可以省略內層的花括號:

MoreData x{{"test1", 6.778}, false};    // 自從C++17起OK
MoreData y{"test1", 6.778, false};      // OK

注意因為現在派生類也可以是聚合體,所以其他的一些初始化方法也可以使用:

MoreData u;     // OOPS:value/done未初始化
MoreData z{};   // OK: value/done初始化為0/false

如果覺得這樣很危險,可以使用成員初始值:

struct Data {
    std::string name;
    double value{0.0};
};
struct Cpp14Data : Data {
    bool done{false};
};

或者,繼續提供一個預設建構函式。

使用聚合體擴充套件

聚合體初始化的一個典型應用場景是對一個派生自C風格結構體並且新增了新成員的類進行初始化。例如:

struct Data {
    const char* name;
    double value;
};
struct CppData : Data {
    bool critical;
    void print() const {
        std::cout << '[' << name << ',' << value << "]n";
    }
};
CppData y{{"test1", 6.778}, false};
y.print();

這裡,內層花括號裡的引數被傳遞給基礎類別Data

注意你可以跳過初始化某些值。在這種情況下,跳過的成員將會進行預設初始化

(基礎型別會被初始化為0false或者nullptr,類型別會預設構造)。

例如:

CppData x1{};           // 所有成員預設初始化為0值
CppData x2{{"msg"}}     // 和{{"msg", 0.0}, false}等價
CppData x3{{}, true};   // 和{{nullptr, 0.0}, true}等價
CppData x4;             // 成員的值未定義

注意使用空花括號和不使用花括號完全不同:

x1的定義會把所有成員預設初始化為0值,

因此字元指標name被初始化為nullptr

double型別的value初始化為0.0

bool型別的flag初始化為falsex4的定義沒有初始化任何成員。所有成員的值都是未定義的。

你也可以從非聚合體派生出聚合體。例如:

struct MyString : std::string {
    void print() const {
        if (empty()) {
            std::cout << "<undefined>n";
        }
        else {
            std::cout << c_str() << 'n';
        }
    }
};
MyString x{{"hello"}};
MyString y{"world"};

注意這不是通常的具有多型性的public繼承,因為std::string沒有虛成員函數,

你需要避免混淆這兩種型別。

你甚至可以從多個基礎類別和聚合體中派生出聚合體:

template<typename T>
struct D : std::string, std::complex<T>
{
    std::string data;
};

你可以像下面這樣使用和初始化:

D<float> s{{"hello"}, {4.5, 6.7}, "world"}; // 自從C++17起OK
D<float> t{"hello", {4.5, 6.7}, "world"};   // 自從C++17起OK
std::cout << s.data;                        // 輸出:"world"
std::cout << static_cast<std::string>(s);   // 輸出:"hello"
std::cout << static_cast<std::complex<float>>(s);   //輸出:(4.5,6.7)

內部巢狀的初值列表將按照繼承時基礎類別宣告的順序傳遞給基礎類別。

這個新的特性也可以幫助我們用很少的程式碼定義過載的lambda

聚合體的定義

總的來說,在C++17中滿足如下條件之一的物件被認為是 聚合體 :

  • 是一個陣列
  • 或者是一個滿足如下條件的 類型別 (classstructunion):
  • 沒有使用者定義的和explicit的建構函式
  • 沒有使用using宣告繼承的建構函式
  • 沒有privateprotected的非靜態資料成員
  • 沒有virtual函數
  • 沒有virtual, private, protected的基礎類別

然而,要想使用聚合體初始化來 初始化 聚合體,那麼還需要滿足如下額外的約束:

  • 基礎類別中沒有private或者protected的成員
  • 沒有private或者protected的建構函式

下一節就有一個因為不滿足這些額外約束導致編譯失敗的例子。

C++17引入了一個新的型別特徵is_aggregate<>

來測試一個型別是否是聚合體:

template<typename T>
struct D : std::string, std::complex<T> {
    std::string data;
};
D<float> s{{"hello"}, {4.5, 6.7}, "world"};         // 自從C++17起OK
std::cout << std::is_aggregate<decltype(s)>::value; // 輸出1(true)

向後的不相容性

注意下面的例子不能再通過編譯:

struct Derived;
struct Base {
    friend struct Derived;
private:
    Base() {
    }
};
struct Derived : Base {
};
int main()
{
    Derived d1{};   // 自從C++17起ERROR
    Derived d2;     // 仍然OK(但可能不會初始化)
}

在C++17之前,Derived不是聚合體。因此

Derived d1{};

會呼叫Derived隱式定義的預設建構函式,這個建構函式會呼叫基礎類別Base的建構函式。

儘管基礎類別的預設建構函式是private的,但在派生類別建構函式裡呼叫它也是有效的,

因為派生類被宣告為友元類。

自從C++17起,例子中的Derived是一個聚合體,所以它沒有隱式的預設建構函式

(建構函式沒有使用using宣告繼承)。因此,d1的初始化將是一個聚合體初始化,

如下表示式:

std::is_aggregate<Derived>::value

將返回true

然而,因為基礎類別有一個private的建構函式(見上一節)所以不能使用花括號來初始化。

這和派生類是否是基礎類別的友元無關。

到此這篇關於C++聚合體初始化aggregate initialization詳細介紹的文章就介紹到這了,更多相關C++聚合體初始化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com