C++
automatyczny
Szukaj…
Uwagi
Słowo kluczowe auto to nazwa typu reprezentująca typ automatycznie wywnioskowany.
To było już zastrzeżone słowo kluczowe w C ++ 98, odziedziczone po C. W starych wersjach C ++ można go było użyć do jawnego stwierdzenia, że zmienna ma automatyczny czas przechowywania:
int main()
{
auto int i = 5; // removing auto has no effect
}
To stare znaczenie zostało teraz usunięte.
Podstawowa próbka automatyczna
auto kluczowe auto zapewnia automatyczne odjęcie typu zmiennej.
Jest to szczególnie wygodne w przypadku długich nazw typów:
std::map< std::string, std::shared_ptr< Widget > > table;
// C++98
std::map< std::string, std::shared_ptr< Widget > >::iterator i = table.find( "42" );
// C++11/14/17
auto j = table.find( "42" );
z pętlami zależnymi od zasięgu :
vector<int> v = {0, 1, 2, 3, 4, 5};
for(auto n: v)
std::cout << n << ' ';
z lambdami :
auto f = [](){ std::cout << "lambda\n"; };
f();
aby uniknąć powtórzenia tego typu:
auto w = std::make_shared< Widget >();
aby uniknąć zaskakujących i niepotrzebnych kopii:
auto myMap = std::map<int,float>();
myMap.emplace(1,3.14);
std::pair<int,float> const& firstPair2 = *myMap.begin(); // copy!
auto const& firstPair = *myMap.begin(); // no copy!
Powodem kopiowania jest to, że zwracanym typem jest w rzeczywistości std::pair<const int,float> !
szablony automatyczne i wyrażenia
auto może również powodować problemy w przypadku użycia szablonów wyrażeń:
auto mult(int c) {
return c * std::valarray<int>{1};
}
auto v = mult(3);
std::cout << v[0]; // some value that could be, but almost certainly is not, 3.
Powodem jest to, że operator* na valarray daje ci obiekt proxy, który odwołuje się do valarray jako sposobu leniwej oceny. Korzystając z auto , tworzysz wiszące odniesienie. Zamiast mult zwrócił std::valarray<int> , wtedy kod na pewno wypisze 3.
auto, const i referencje
Samo słowo kluczowe auto reprezentuje typ wartości podobny do int lub char . Można go modyfikować za pomocą słowa kluczowego const i symbolu & aby reprezentować odpowiednio typ const lub typ odwołania. Te modyfikatory można łączyć.
W tym przykładzie s jest typem wartości (jego typ będzie wywnioskowany jako std::string ), więc każda iteracja pętli for kopiuje ciąg z wektora do s .
std::vector<std::string> strings = { "stuff", "things", "misc" };
for(auto s : strings) {
std::cout << s << std::endl;
}
Jeśli s.append(" and stuff") pętli modyfikuje s (na przykład przez wywołanie s.append(" and stuff") ), tylko ta kopia zostanie zmodyfikowana, a nie oryginalny element strings .
Z drugiej strony, jeśli s jest zadeklarowane za pomocą auto& będzie to typ odwołania (domyślnie jest to std::string& ), więc przy każdej iteracji pętli zostanie przypisane odwołanie do ciągu w wektorze:
for(auto& s : strings) {
std::cout << s << std::endl;
}
W treści tej pętli modyfikacje s będą miały bezpośredni wpływ na element strings których się odwołuje.
Na koniec, jeśli s zostanie zadeklarowane jako const auto& , będzie to stały typ odwołania, co oznacza, że przy każdej iteracji pętli zostanie mu przypisane stałe odniesienie do łańcucha w wektorze:
for(const auto& s : strings) {
std::cout << s << std::endl;
}
W ramach tego ciała pętli, s nie mogą być zmienione (np żadnych metod const mogą być wywoływane na nim).
Kiedy używasz auto z pętlą opartą for , ogólnie dobrą praktyką jest używanie const auto& jeśli korpus pętli nie zmodyfikuje zapętlonej struktury, ponieważ pozwala to uniknąć niepotrzebnych kopii.
Końcowy typ zwrotu
auto jest używane w składni dla końcowego typu powrotu:
auto main() -> int {}
co jest równoważne z
int main() {}
Najbardziej użyteczny w połączeniu z decltype do użycia parametrów zamiast std::declval<T> :
template <typename T1, typename T2>
auto Add(const T1& lhs, const T2& rhs) -> decltype(lhs + rhs) { return lhs + rhs; }
Ogólna lambda (C ++ 14)
C ++ 14 pozwala na użycie auto w argumencie lambda
auto print = [](const auto& arg) { std::cout << arg << std::endl; };
print(42);
print("hello world");
Ta lambda jest w większości odpowiednikiem
struct lambda {
template <typename T>
auto operator ()(const T& arg) const {
std::cout << arg << std::endl;
}
};
i wtedy
lambda print;
print(42);
print("hello world");
obiekty auto i proxy
Czasami auto może zachowywać się niezupełnie tak, jak oczekiwał tego programista. Typ dedukuje wyrażenie, nawet jeśli dedukcja typu nie jest właściwa.
Na przykład, gdy w kodzie są używane obiekty proxy:
std::vector<bool> flags{true, true, false};
auto flag = flags[0];
flags.push_back(true);
flag nie byłaby bool , ale std::vector<bool>::reference , ponieważ dla specjalizacji bool vector szablonowy operator [] zwraca obiekt proxy z zdefiniowanym operatorem konwersji operator bool .
Gdy flags.push_back(true) modyfikuje kontener, to pseudo-odwołanie może w końcu zwisać, odnosząc się do elementu, który już nie istnieje.
Umożliwia także następną sytuację:
void foo(bool b);
std::vector<bool> getFlags();
auto flag = getFlags()[5];
foo(flag);
vector jest natychmiast odrzucany, więc flag jest pseudo-odniesieniem do elementu, który został odrzucony. Wezwanie do foo wywołuje niezdefiniowane zachowanie.
W takich przypadkach możesz zadeklarować zmienną za pomocą auto i zainicjować ją, rzutując na typ, który chcesz wywnioskować:
auto flag = static_cast<bool>(getFlags()[5]);
ale w tym momencie po prostu zastąpienie auto bool ma większy sens.
Innym przypadkiem, w którym obiekty proxy mogą powodować problemy, są szablony wyrażeń . W takim przypadku szablony czasami nie są zaprojektowane tak, aby przetrwały poza bieżącym pełnym wyrażeniem ze względu na wydajność, a użycie obiektu proxy w następnym powoduje niezdefiniowane zachowanie.