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

std::array nie ogranicza się do elementów typów podstawowych. Zamiast tego elementy 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 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 { // użyj CTAD, aby wydedukować argumenty szablonu <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 }, // tutaj wspominamy o House
            House{ 14, 2, 5 }, // i tutaj
            House{ 15, 2, 4 }  // i tutaj
        };

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

    // Kompilator wie, że każdy element domu jest domem
    // więc niejawnie konwertuje prawą stronę każdego przypisania na 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 { // mówimy kompilatorowi, że każdy element jest domem
            { 13, 1, 7 }, // ale nie wspominając o tym tutaj
            { 14, 2, 5 },
            { 15, 2, 4 } 
        };

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

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]; // tablica w stylu C z N elementów typu 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 { // inicjator dla domów
    { 13, 1, 7 }, // inicjator elementu tablicy w stylu C z nazwą_definiowaną_implementacją
    { 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:

// Działa zgodnie z oczekiwaniami
constexpr std::array<House, 3> houses { // inicjator dla domów
    { // dodatkowy zestaw nawiasów klamrowych do inicjalizacji elementu tablicy w stylu C z nazwą_definiowaną_implementacji
        { 13, 4, 30 }, // inicjator elementu tablicy 0
        { 14, 3, 10 }, // inicjator elementu tablicy 1
        { 15, 3, 40 }, // inicjator elementu tablicy 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 {{ // zwróć uwagę na podwójne nawiasy klamrowe
        { 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>

// Każdy uczeń ma identyfikator i nazwę
struct Student
{
	int id{};
	std::string_view name{};
};

// Nasza tablica 3 studentów (pojedyncze nawiasy klamrowe, ponieważ wspominamy o Studentach przy każdym inicjatorze)
constexpr std::array students{ Student{0, "Alex"}, Student{ 1, "Joe" }, Student{ 2, "Bob" } };

const Student* findStudentById(int id)
{
	// Przejrzyj wszystkich uczniów
	for (auto& s : students)
	{
		// Zwróć ucznia z pasującym identyfikatorem
		if (s.id == id) return &s;
	}

	// Nie znaleziono pasującego identyfikatora
	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