Obiekt typu std::vector można przekazać do funkcji, tak jak każdy inny obiekt. Oznacza to, że jeśli miniemy a 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) // we must explicitly specify <int> here
{
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: this is a std::vector<int>
std::vector dbl{ 1.1, 2.2, 3.3 };
passByRef(dbl); // compile error: std::vector<double> is not convertible to 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) // compile error: CTAD can't be used to infer function parameters
{
std::cout << arr[0] << '\n';
}
int main()
{
std::vector primes{ 2, 3, 5, 7, 11 }; // okay: use CTAD to infer 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: compiler will instantiate passByRef(const std::vector<int>&)
std::vector dbl{ 1.1, 2.2, 3.3 };
passByRef(dbl); // ok: compiler will instantiate 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) // will accept any type of object that has an overloaded operator[]
{
std::cout << arr[0] << '\n';
}
int main()
{
std::vector primes{ 2, 3, 5, 7, 11 };
passByRef(primes); // ok: compiler will instantiate passByRef(const std::vector<int>&)
std::vector dbl{ 1.1, 2.2, 3.3 };
passByRef(dbl); // ok: compiler will instantiate 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) // abbreviated function template
{
std::cout << arr[0] << '\n';
}
int main()
{
std::vector primes{ 2, 3, 5, 7, 11 };
passByRef(primes); // ok: compiler will instantiate passByRef(const std::vector<int>&)
std::vector dbl{ 1.1, 2.2, 3.3 };
passByRef(dbl); // ok: compiler will instantiate 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, a 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 }; // a 2-element array (valid indexes 0 and 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: a 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>
// Write your printElement function here
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

