16.4 — Zaliczenie std::vector

Obiekt typu std::vector można przekazać do funkcji, tak jak każdy inny obiekt. Oznacza to, że jeśli miniemy std::vector według wartości zostanie wykonana kosztowna kopia. Dlatego zazwyczaj przechodzimy std::vector przez (const) odniesienie, aby uniknąć takich kopii.

Dzięki std::vector, typ elementu jest częścią informacji o typie obiektu. Dlatego też, gdy używamy std::vector jako parametru funkcji, musimy jawnie określić typ elementu:

#include <iostream>
#include <vector>

void passByRef(const std::vector<int>& arr) // musimy jawnie podaj tutaj <int>
{
    std::cout << arr[0] << '\n';
}

int main()
{
    std::vector primes{ 2, 3, 5, 7, 11 };
    passByRef(primes);

    return 0;
}

Przekazywanie std::vector różnych typów elementów

Ponieważ nasz passByRef() funkcja oczekuje std::vector<int>, nie jesteśmy w stanie przekazywać wektorów z różnymi typami elementów:

#include <iostream>
#include <vector>

void passByRef(const std::vector<int>& arr)
{
    std::cout << arr[0] << '\n';
}

int main()
{
    std::vector primes{ 2, 3, 5, 7, 11 };
    passByRef(primes);  // ok: żartuję std::vector<int>

    std::vector dbl{ 1.1, 2.2, 3.3 };
    passByRef(dbl); // błąd kompilacji: std::vector<double> nie można konwertować na std::vector<int>

    return 0;
}

W C++ 17 lub nowszym możesz spróbować rozwiązać ten problem za pomocą CTAD problem:

#include <iostream>
#include <vector>

void passByRef(const std::vector& arr) // błąd kompilacji: nie można użyć CTAD do wnioskowania o parametrach funkcji
{
    std::cout << arr[0] << '\n';
}

int main()
{
    std::vector primes{ 2, 3, 5, 7, 11 }; // OK: użyj CTAD, aby wywnioskować std::vector<int>
    passByRef(primes);

    return 0;
}

Chociaż CTAD będzie działać, aby wywnioskować typ elementu wektora z inicjatorów, gdy jest on zdefiniowany, CTAD nie działa (obecnie) z parametrami funkcji.

Widzieliśmy tego rodzaju problem już wcześniej, gdy przeciążono funkcje, które różnią się tylko typem parametru. To świetne miejsce do wykorzystania szablonów funkcji! Możemy utworzyć szablon funkcji, który parametryzuje typ elementu, a następnie C++ użyje tego szablonu funkcji do utworzenia instancji funkcji z rzeczywistymi typami.

Powiązana treść

Szablony funkcji omawiamy w lekcji 11.6 -- Szablony funkcji.

Możemy utworzyć szablon funkcji, który używa tej samej deklaracji parametrów szablonu:

#include <iostream>
#include <vector>

template <typename T>
void passByRef(const std::vector<T>& arr)
{
    std::cout << arr[0] << '\n';
}

int main()
{
    std::vector primes{ 2, 3, 5, 7, 11 };
    passByRef(primes); // ok: kompilator utworzy instancję passByRef(const std::vector<int>&)

    std::vector dbl{ 1.1, 2.2, 3.3 };
    passByRef(dbl);    // ok: kompilator utworzy instancję passByRef(const std::vector<double>&)

    return 0;
}

W powyższym przykładzie utworzyliśmy szablon pojedynczej funkcji o nazwie passByRef() , którego parametr typu const std::vector<T>&. T jest zdefiniowany w deklaracji parametrów szablonu w poprzedniej linii: template <typename T. T jest parametrem szablonu typu standardowego, który pozwala wywołującemu określić element type.

Dlatego, gdy wywołamy passByRef(primes) z main() (gdzie primes jest zdefiniowane jako std::vector<int>), kompilator utworzy instancję i wywoła void passByRef(const std::vector<int>& arr).

Kiedy wywołujemy passByRef(dbl) z main() (gdzie dbl jest zdefiniowane jako std::vector<double>), kompilator utworzy instancję i wywoła void passByRef(const std::vector<double>& arr).

W ten sposób stworzyliśmy pojedynczy szablon funkcji, który może tworzyć instancje funkcji do obsługi std::vector argumentów dowolnego typu i długości elementu!

Przekazywanie std::vector przy użyciu szablonu ogólnego lub skróconego szablonu funkcji

Możemy również utworzyć szablon funkcji, który zaakceptuje dowolny typ obiekt:

#include <iostream>
#include <vector>

template <typename T>
void passByRef(const T& arr) // zaakceptuje dowolny typ obiektu, który ma przeciążony operator[]
{
    std::cout << arr[0] << '\n';
}

int main()
{
    std::vector primes{ 2, 3, 5, 7, 11 };
    passByRef(primes); // ok: kompilator utworzy instancję passByRef(const std::vector<int>&)

    std::vector dbl{ 1.1, 2.2, 3.3 };
    passByRef(dbl);    // ok: kompilator utworzy instancję passByRef(const std::vector<double>&)

    return 0;
}

W C++20 możemy użyć skróconego szablonu funkcji (poprzez parametr auto ), aby zrobić to samo:

#include <iostream>
#include <vector>

void passByRef(const auto& arr) // skrócona funkcja szablon
{
    std::cout << arr[0] << '\n';
}

int main()
{
    std::vector primes{ 2, 3, 5, 7, 11 };
    passByRef(primes); // ok: kompilator utworzy instancję passByRef(const std::vector<int>&)

    std::vector dbl{ 1.1, 2.2, 3.3 };
    passByRef(dbl);    // ok: kompilator utworzy instancję passByRef(const std::vector<double>&)

    return 0;
}

Obydwa akceptują argument typu dowolnego , który zostanie skompilowany. Może to być pożądane podczas pisania funkcji, na których chcielibyśmy operować nie tylko std::vector. Na przykład powyższe funkcje będą działać również na std::array, std::string lub innym typie, którego być może nawet nie rozważaliśmy.

Potencjalną wadą tej metody jest to, że może ona prowadzić do błędów, jeśli do funkcji zostanie przekazany obiekt typu, który się kompiluje, ale nie ma sensu semantycznie.

Twierdzenie na podstawie długości tablicy

Rozważ następującą funkcję szablonu, która jest podobna do tej przedstawionej powyżej:

#include <iostream>
#include <vector>

template <typename T>
void printElement3(const std::vector<T>& arr)
{
    std::cout << arr[3] << '\n';
}

int main()
{
    std::vector arr{ 9, 7, 5, 3, 1 };
    printElement3(arr);

    return 0;
}

Chociaż printElement3(arr) działa dobrze w tym przypadku, w tym programie na nieostrożnego programistę czeka potencjalny błąd. Widzisz to?

Powyższy program wypisuje wartość elementu tablicy o indeksie 3. Jest to w porządku, o ile tablica zawiera prawidłowy element o indeksie 3. Jednakże kompilator z radością pozwoli Ci przekazać tablice, w których indeks 3 jest poza dopuszczalnym zakresem. Na przykład:

#include <iostream>
#include <vector>

template <typename T>
void printElement3(const std::vector<T>& arr)
{
    std::cout << arr[3] << '\n';
}

int main()
{
    std::vector arr{ 9, 7 }; // tablica 2-elementowa (ważne indeksy 0 i 1)
    printElement3(arr);

    return 0;
}

Prowadzi to do niezdefiniowanego zachowanie.

Jedną z opcji jest tutaj potwierdzenie on arr.size(), które wyłapie takie błędy po uruchomieniu w konfiguracji kompilacji debugowania. Ponieważ std::vector::size() nie jest funkcją constexpr, możemy tutaj wykonać jedynie asercję w czasie wykonywania.

Wskazówka

Lepszą opcją jest unikanie używania std::vectorw przypadkach, gdy trzeba potwierdzić długość tablicy. Użycie typu obsługującego constexpr tablice (np. std::array) jest prawdopodobnie lepszym wyborem, ponieważ można static_assert na długości tablicy constexpr. Omówimy to w przyszłej lekcji 17.3 — Przekazywanie i zwracanie std::array.

Najlepszą opcją jest unikanie pisania funkcji, które polegają na podaniu przez użytkownika wektora o minimalnej długości.

Czas quizu

Pytanie nr 1

Napisz funkcję, która przyjmuje dwa parametry: std::vector i indeks. Jeśli indeks jest poza zakresem, wydrukuj błąd. Jeżeli indeks mieści się w granicach, wypisz wartość elementu.

Następujący przykładowy program powinien się skompilować:

#include <iostream>
#include <vector>

// Napisz swoją funkcję printElement tutaj

int main()
{
    std::vector v1 { 0, 1, 2, 3, 4 };
    printElement(v1, 2);
    printElement(v1, 5);

    std::vector v2 { 1.1, 2.2, 3.3 };
    printElement(v2, 0);
    printElement(v2, -1);

    return 0;
}

i dać następujący wynik:

The element has value 2
Invalid index
The element has value 1.1
Invalid index

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:  
100 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze