17.4 — std::array typów klas i eliminacja nawiasów

A std::array nie ogranicza się do elementów typów podstawowych. Zamiast tego elementy a std::array mogą być obiektami dowolnego typu, łącznie z typami złożonymi. Oznacza to, że możesz utworzyć std::array wskaźniki lub std::array struktury (lub klasy)

Jednak inicjowanie std::array struktur lub klas zwykle wprawia w zakłopotanie nowych programistów, dlatego poświęcimy lekcję szczegółowo omawiającą ten temat.

Nota autora

Użyjemy struktur, aby zilustrować nasze uwagi w tej lekcji. Materiał odnosi się równie dobrze do klas.

Definiowanie i przypisywanie std::array z structs

Zacznijmy od prostej struktury:

struct House
{
    int number{};
    int stories{};
    int roomsPerStory{};
};

Defining a std::array z House a przypisywanie elementów działa tak, jak można się spodziewać:

#include <array>
#include <iostream>

struct House
{
    int number{};
    int stories{};
    int roomsPerStory{};
};

int main()
{
    std::array<House, 3> houses{};

    houses[0] = { 13, 1, 7 };
    houses[1] = { 14, 2, 5 };
    houses[2] = { 15, 2, 4 };

    for (const auto& house : houses)
    {
        std::cout << "House number " << house.number
                  << " has " << (house.stories * house.roomsPerStory)
                  << " rooms.\n";
    }

    return 0;
}

Powyższe daje następujące wyniki:

House number 13 has 7 rooms.
House number 14 has 10 rooms.
House number 15 has 8 rooms.

Inicjowanie std::array z structs

Inicjowanie tablicy struktur również działa tak, jak można się spodziewać, pod warunkiem, że wyraźnie określisz typ elementu:

#include <array>
#include <iostream>

struct House
{
    int number{};
    int stories{};
    int roomsPerStory{};
};

int main()
{
    constexpr std::array houses { // use CTAD to deduce template arguments <House, 3>
            House{ 13, 1, 7 },
            House{ 14, 2, 5 },
            House{ 15, 2, 4 }
        };

    for (const auto& house : houses)
    {
        std::cout << "House number " << house.number
            << " has " << (house.stories * house.roomsPerStory)
            << " rooms.\n";
    }

    return 0;
}

W powyższym przykładzie używamy CTAD do wydedukowania typu std::array jak std::array<House, 3>. Następnie udostępniamy 3 House obiekty jako inicjatory, co działa całkiem nieźle.

Inicjalizacja bez jawnego określenia typu elementu dla każdego inicjatora

W powyższym przykładzie można zauważyć, że każdy inicjator wymaga od nas podania typu elementu:

    constexpr std::array houses {
            House{ 13, 1, 7 }, // we mention House here
            House{ 14, 2, 5 }, // and here
            House{ 15, 2, 4 }  // and here
        };

Ale nie musieliśmy robić tego samego w przypadku przypisania:

    // The compiler knows that each element of houses is a House
    // so it will implicitly convert the right hand side of each assignment to a House
    houses[0] = { 13, 1, 7 };
    houses[1] = { 14, 2, 5 };
    houses[2] = { 15, 2, 4 };

Możesz więc pomyśl o wypróbowaniu czegoś takiego:

    // doesn't work
    constexpr std::array<House, 3> houses { // we're telling the compiler that each element is a House
            { 13, 1, 7 }, // but not mentioning it here
            { 14, 2, 5 },
            { 15, 2, 4 } 
        };

Być może, co zaskakujące, to nie działa. Przyjrzyjmy się dlaczego.

A std::array jest zdefiniowany jako struktura zawierająca pojedynczy element tablicy w stylu C (którego nazwa jest zdefiniowana w implementacji), w następujący sposób:

template<typename T, std::size_t N>
struct array
{
    T implementation_defined_name[N]; // a C-style array with N elements of type T
}

Nota autora

Nie omawialiśmy jeszcze tablic w stylu C, ale na potrzeby tej lekcji wszystko, co musisz wiedzieć to to, że T implementation_defined_name[N]; jest tablicą o stałym rozmiarze N elementów typu T (podobnie jak std::array<T, N> implementation_defined_name;).

Omówimy tablicę w stylu C arrays w nadchodzącej lekcji 17.7 -- Wprowadzenie do tablic w stylu C.

Więc gdy spróbujemy zainicjować houses zgodnie z powyższym, kompilator interpretuje inicjalizację w następujący sposób:

// Doesn't work
constexpr std::array<House, 3> houses { // initializer for houses
    { 13, 1, 7 }, // initializer for C-style array member with implementation_defined_name
    { 14, 2, 5 }, // ?
    { 15, 2, 4 }  // ?
};

Kompilator zinterpretuje { 13, 1, 7 } jako inicjator dla pierwszego elementu houses, czyli tablicy w stylu C z nazwą zdefiniowaną w implementacji. Spowoduje to zainicjowanie elementu tablicy w stylu C 0 with { 13, 1, 7 } , a pozostałe elementy zostaną zainicjowane zerem. Następnie kompilator odkryje, że udostępniliśmy dwie dodatkowe wartości inicjujące ({ 14, 2, 7 } i { 15, 2, 5 }) i zwróci błąd kompilacji informujący nas, że podaliśmy zbyt wiele wartości inicjujących.

Właściwy sposób zainicjowania powyższego polega na dodaniu dodatkowego zestawu nawiasów klamrowych w następujący sposób:

// This works as expected
constexpr std::array<House, 3> houses { // initializer for houses
    { // extra set of braces to initialize the C-style array member with implementation_defined_name
        { 13, 4, 30 }, // initializer for array element 0
        { 14, 3, 10 }, // initializer for array element 1
        { 15, 3, 40 }, // initializer for array element 2
     }
};

Zwróć uwagę na wymagany dodatkowy zestaw nawiasów klamrowych. (aby rozpocząć inicjalizację elementu tablicy w stylu C wewnątrz struktury std::array ). W tych nawiasach możemy następnie zainicjować każdy element indywidualnie, każdy wewnątrz własnego zestawu nawiasów.

Dlatego zobaczysz std::array inicjatory z dodatkowym zestawem nawiasów klamrowych, gdy typ elementu wymaga listy wartości i nie podajemy jawnie typu elementu jako części inicjatora.

Kluczowa informacja

Podczas inicjalizacji std::array ze strukturą, klasą lub tablicą i bez podawania typu elementu przy każdym inicjatorze, będziesz potrzebować dodatkowej pary nawiasów klamrowych, aby kompilator prawidłowo zinterpretował to, co należy inicjować.

Jest to artefakt inicjalizacji agregowanej, a inne standardowe typy kontenerów bibliotek (które używają konstruktorów list) nie wymagają w takich przypadkach podwójnych nawiasów klamrowych.

Oto pełny przykład:

#include <array>
#include <iostream>

struct House
{
    int number{};
    int stories{};
    int roomsPerStory{};
};

int main()
{
    constexpr std::array<House, 3> houses {{ // note double braces
        { 13, 1, 7 },
        { 14, 2, 5 },
        { 15, 2, 4 }
    }};

    for (const auto& house : houses)
    {
        std::cout << "House number " << house.number
                  << " has " << (house.stories * house.roomsPerStory)
                  << " rooms.\n";
    }

    return 0;
}

Elizja nawiasów dla agregaty

Biorąc pod uwagę powyższe wyjaśnienie, możesz się zastanawiać, dlaczego powyższy przypadek wymaga podwójnych nawiasów klamrowych, podczas gdy wszystkie inne przypadki, które widzieliśmy, wymagają tylko pojedynczych nawiasów klamrowych:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array<int, 5> arr { 1, 2, 3, 4, 5 }; // single braces

    for (const auto n : arr)
        std::cout << n << '\n';

    return 0;
}

Okazuje się, że możesz podać podwójne nawiasy dla takich tablic:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array<int, 5> arr {{ 1, 2, 3, 4, 5 }}; // double braces

    for (const auto n : arr)
        std::cout << n << '\n';

    return 0;
}

Jednak agregaty w C++ obsługują koncepcję zwaną elizja nawiasów, która określa pewne zasady dotyczące sytuacji, gdy może występować wiele nawiasów klamrowych. ogólnie rzecz biorąc, można pominąć nawiasy klamrowe podczas inicjowania std::array za pomocą wartości skalarnych (pojedynczych) lub podczas inicjowania z typami klasowymi lub tablicami, gdzie typ jest jawnie nazwany przy każdym elemencie.

Nie ma nic złego w ciągłej inicjalizacji std::array z podwójnymi nawiasami klamrowymi, ponieważ pozwala uniknąć konieczności zastanawiania się, czy elizja nawiasów ma zastosowanie w konkretnym przypadku, czy nie. Alternatywnie możesz spróbować wykonać inicjację z pojedynczym nawiasem klamrowym, a kompilator zazwyczaj będzie narzekał, jeśli nie będzie mógł tego rozgryźć. W takim przypadku możesz szybko dodać dodatkowy zestaw nawiasów klamrowych.

Kolejny przykład

Oto jeszcze jeden przykład inicjowania std::array z Student struktur.

#include <array>
#include <iostream>
#include <string_view>

// Each student has an id and a name
struct Student
{
	int id{};
	std::string_view name{};
};

// Our array of 3 students (single braced since we mention Student with each initializer)
constexpr std::array students{ Student{0, "Alex"}, Student{ 1, "Joe" }, Student{ 2, "Bob" } };

const Student* findStudentById(int id)
{
	// Look through all the students
	for (auto& s : students)
	{
		// Return student with matching id
		if (s.id == id) return &s;
	}

	// No matching id found
	return nullptr;
}

int main()
{
	constexpr std::string_view nobody { "nobody" };

	const Student* s1 { findStudentById(1) };
	std::cout << "You found: " << (s1 ? s1->name : nobody) << '\n';

	const Student* s2 { findStudentById(3) };
	std::cout << "You found: " << (s2 ? s2->name : nobody) << '\n';

	return 0;
}

Wypisuje:

You found: Joe
You found: nobody

Zauważ, że ponieważ std::array students to constexpr, nasza findStudentById() funkcja musi zwracać wskaźnik const, co oznacza, że nasze Student wskaźniki w main() muszą również być const.

Czas quizu

Pytanie nr 1

Zdefiniuj struktura o nazwie Item która zawiera dwa elementy: std::string_view name i int gold. Zdefiniuj std::array i zainicjuj go za pomocą 4 obiektów Item. Użyj CTAD, aby wydedukować typ elementu i rozmiar tablicy.

Pokaż wskazówkę

Program powinien wydrukować następujące informacje:

A sword costs 5 gold.
A dagger costs 3 gold.
A club costs 2 gold.
A spear costs 7 gold.

Pokaż rozwiązanie

Pytanie nr 2

Zaktualizuj swoje rozwiązanie do quizu 1, aby nie określać jawnie typu elementu dla każdego inicjator.

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
55 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze