W tej prostej definicji zmiennej kryje się subtelna redundancja:
double d{ 5.0 };W C++ wymagane jest podanie jawnego typu dla wszystkich obiektów. Dlatego określiliśmy, że zmienna d jest typu double.
Jednak wartość literału 5.0 użyta do inicjalizacji d również ma typ double (domyślnie określony poprzez format literału).
Powiązana treść
Omówimy sposób określania typów literału na lekcji 5.2 -- Literały.
W przypadkach, gdy chcemy, aby zmienna i jej inicjator miały ten sam typ, w efekcie dwukrotnie dostarczamy informację o tym samym typie.
Dedukcja typu dla zainicjowanych zmiennych
Dedukcja typu (czasami nazywane wnioskowanie typu) to funkcja, która pozwala kompilatorowi wydedukować typ obiektu na podstawie inicjatora obiektu. Definiując zmienną, można wywołać dedukcję typu za pomocą słowa kluczowego auto, które można zastosować zamiast typu zmiennej:
int main()
{
auto d { 5.0 }; // 5.0 jest literałem podwójnym, więc d zostanie wydedukowane jako double
auto i { 1 + 2 }; // 1 + 2 daje wartość int, więc i zostanie wydedukowane jako int
auto x { i }; // i jest int, więc x zostanie wydedukowane jako int
return 0;
}W pierwszym przypadku, ponieważ 5.0 jest literałem podwójnym, kompilator wydedukuje, że zmienna d powinna być typu double. W drugim przypadku wyrażenie 1 + 2 zwraca wynik typu int, więc zmienna i będzie typu int. W trzecim przypadku i została wcześniej wydedukowana jako typu int, więc x będzie również wywnioskowana jako typ int.
Ostrzeżenie
przed C++17, auto d{ 5.0 }; wywnioskowałoby d by wpisz std::initializer_list<double> zamiast double. Zostało to naprawione w C++ 17 i wiele kompilatorów (takich jak gcc i Clang) przeniosło tę zmianę do poprzednich standardów językowych.
Jeśli używasz C++14 lub starszego, a powyższy przykład nie kompiluje się na twoim kompilatorze, użyj zamiast tego inicjalizacji kopiowania za pomocą auto (auto d = 5.0).
Ponieważ wywołania funkcji są prawidłowymi wyrażeniami, możemy nawet użyć dedukcji typu, gdy naszym inicjatorem jest niepuste wywołanie funkcji:
int add(int x, int y)
{
return x + y;
}
int main()
{
auto sum { add(5, 6) }; // add() zwraca wartość int, więc typ sumy zostanie wydedukowany jako int
return 0;
}Klasa add() funkcja zwraca int wartość, więc kompilator wywnioskowuje, że zmienna sum powinna mieć typ int.
Sufiksy literałów mogą być używane w połączeniu z dedukcją typu, aby określić konkretny typ:
int main()
{
auto a { 1.23f }; // Przyrostek f powoduje, że a jest traktowane jako liczba zmiennoprzecinkowa
auto b { 5u }; // powodujesz wydedukowanie b jako unsigned int
return 0;
}Zmienne korzystające z dedukcji typów mogą również używać innych specyfikatorów/kwalifikatorów, takich jak as const lub constexpr:
int main()
{
int a { 5 }; // a jest int
const auto b { 5 }; // b jest stałą int
constexpr auto c { 5 }; // c jest constexpr int
return 0;
}Dedukcja typu musi mieć coś, z czego można wydedukować
Dedukcja typu nie będzie działać w przypadku obiektów, które albo nie mają inicjatorów, albo mają puste inicjatory. Nie będzie również działać, gdy inicjator ma typ void (lub inny niekompletny typ). Zatem poniższe nie jest prawidłowe:
#include <iostream>
void foo()
{
}
int main()
{
auto a; // Kompilator nie jest w stanie wydedukować typu a
auto b { }; // Kompilator nie jest w stanie wydedukować typu b
auto c { foo() }; // Nieprawidłowy: c nie może mieć typu niekompletnego typu void
return 0;
}Chociaż użycie dedukcji typu dla podstawowych typów danych pozwala zaoszczędzić tylko kilka (jeśli istnieją) naciśnięcia klawiszy, w przyszłych lekcjach zobaczymy przykłady, w których typy stają się skomplikowane i długie (a w niektórych przypadkach mogą być trudne do zrozumienia). W takich przypadkach użycie auto może zaoszczędzić dużo pisania (i literówek).
Powiązana treść
Zasady dedukcji typów dla wskaźników i referencji są nieco bardziej złożone. Omówimy je w 12.14 -- Dedukcja typów ze wskaźnikami, referencjami i referencjami. const.
Dedukcja typu spada const z wydedukowanego typu
W większości przypadków dedukcja typu powoduje usunięcie const z wydedukowanych typów. Na przykład:
int main()
{
const int a { 5 }; // a ma typ const int
auto b { a }; // b has type int (const dropped)
return 0;
}W powyższym przykładzie a ma typ const int, ale podczas dedukcji typu dla zmiennej b za pomocą a jako inicjatora, dedukcja typu dedukuje typ. as int, nie const int.
Jeśli chcesz, aby wydedukowany typ był stały, musisz podać const sam w ramach definicji:
int main()
{
const int a { 5 }; // a ma typ const int
const auto b { a }; // b ma typ const int (const został usunięty, ale zastosowany ponownie)
return 0;
}W tym przykładzie typ wydedukowany z a będzie int (const został usunięty), ale ponieważ ponownie dodaliśmy kwalifikator const podczas definiowania zmiennej b, zmienna b będzie miał type const int.
Dedukcja typu dla literałów łańcuchowych
Ze względów historycznych literały łańcuchowe w C++ mają dziwny typ. Dlatego poniższe prawdopodobnie nie będą działać zgodnie z oczekiwaniami:
auto s { "Hello, world" }; // s będzie typu const char*, a nie std::stringJeśli chcesz, aby typem wydedukowanym z literału ciągu był std::string lub std::string_view, będziesz musiał użyć s lub sv sufiksów literału (wprowadzonych na lekcjach 5.7 — Wprowadzenie do std::string i 5.8 — Wprowadzenie do std::string_view):
#include <string>
#include <string_view>
int main()
{
using namespace std::literals; // najłatwiejszy sposób dostępu do sufiksów s i sv
auto s1 { "goo"s }; // „goo”s to literał std::string, więc s1 zostanie wydedukowane jako std::string
auto s2 { "moo"sv }; // "moo"sv jest literałem std::string_view, zatem s2 zostanie wydedukowane jako std::string_view
return 0;
}Ale w takich przypadkach może być lepiej nie używać dedukcji typu.
Dedukcja typów i constexpr
Ponieważ constexpr nie jest częścią systemu typów, nie można ich wywnioskować w ramach dedukcji typów. Jednakże constexpr zmienna jest domyślnie stała i ta stała zostanie pominięta podczas dedukcji typu (i może zostać odczytana w razie potrzeby):
int main()
{
constexpr double a { 3.4 }; // a ma typ const double (constexpr nie jest częścią typu, const jest niejawna)
auto b { a }; // b has type double (const dropped)
const auto c { a }; // c ma typ const double (const usunięty, ale zastosowany ponownie)
constexpr auto d { a }; // d ma typ const double (const usunięty, ale domyślnie ponownie zastosowany przez constexpr)
return 0;
}Korzyści i wady dedukcji typu
Dedukcja typu jest nie tylko wygodna, ale ma także szereg innych korzyści.
Po pierwsze, jeśli w kolejnych wierszach zdefiniowano dwie lub więcej zmiennych, nazwy zmiennych zostaną ułożone w jednej linii, co pomaga zwiększyć czytelność:
// użytkowe do odczytania
int a { 5 };
double b { 6.7 };
// łatwiejszy do odczytania
auto c { 5 };
auto d { 6.7 };Po drugie, dedukcja typu działa tylko w przypadku zmiennych, które mają inicjatory, więc jeśli masz zwyczaj korzystania z dedukcji typów, może pomóc uniknąć niezamierzonego niezainicjowania zmiennych:
int x; // ups, zapomnieliśmy zainicjować x, ale kompilator może nie narzekać
auto y; // kompilator wyświetli błąd, ponieważ nie może wydedukować typu dla yPo trzecie, masz gwarancję, że nie będzie żadnych niezamierzonych konwersji wpływających na wydajność:
std::string_view getString(); // jakaś funkcja, która zwraca std::string_view
std::string s1 { getString() }; // bad: kosztowna konwersja ze std::string_view na std::string (zakładając, że tego nie chciałeś)
auto s2 { getString() }; // dobrze: nie jest wymagana konwersjaDedukcja typów ma również kilka wady.
Po pierwsze, dedukcja typu zaciemnia informację o typie obiektu w kodzie. Chociaż dobre IDE powinno być w stanie pokazać wydedukowany typ (np. po najechaniu myszką na zmienną), nadal nieco łatwiej jest popełnić błędy związane z typem podczas korzystania z dedukcji typów.
Na przykład:
auto y { 5 }; // ups, chcieliśmy tutaj podwójnego, ale przez przypadek podaliśmy literał intW powyższym kodzie, gdybyśmy jawnie określili y jako typ double, y byłoby to double, nawet jeśli przypadkowo udostępniliśmy inicjator dosłownego int. W przypadku dedukcji typu y zostanie wywnioskowany, że jest typu int.
Oto kolejny przykład:
#include <iostream>
int main()
{
auto x { 3 };
auto y { 2 };
std::cout << x / y << '\n'; // ups, chcieliśmy podziału zmiennoprzecinkowego tutaj
return 0;
}W tym przykładzie mniej jasne jest, że otrzymujemy dzielenie na liczbach całkowitych, a nie dzielenie zmiennoprzecinkowe.
Podobne przypadki mają miejsce, gdy zmienna jest unsigned. Ponieważ nie chcemy mieszać wartości ze znakiem i bez znaku, jawna wiedza, że zmienna ma typ bez znaku, jest na ogół czymś, czego nie należy ukrywać.
Po drugie, jeśli zmieni się typ inicjatora, typ zmiennej korzystającej z dedukcji typu również ulegnie zmianie, być może nieoczekiwanie. Rozważ:
auto sum { add(5, 6) + gravity };Jeśli typ zwracany add zmieni się z int na double lub gravity zmieni się z int na double, sum zmieni także typ z int na double.
Ogólnie rzecz biorąc, współczesny konsensus jest taki, że dedukcja typu jest ogólnie bezpieczna w przypadku obiektów i że może to pomóc w zwiększeniu czytelności kodu pomniejszanie informacji o typie, aby logika kodu lepiej się wyróżniała.
Najlepsza praktyka
Użyj dedukcji typów dla swoich zmiennych, gdy typ obiektu nie ma znaczenia.
Preferuj typ jawny, gdy potrzebujesz określonego typu, który różni się od typu inicjatora lub gdy obiekt jest używany w kontekście, w którym przydatne jest uczynienie typu oczywistym.
Nota autora
W przyszłych lekcjach będziemy nadal używać typów jawnych dedukcji typu, gdy uważamy, że pokazanie informacji o typie jest pomocne w zrozumieniu koncepcji lub przykładu.

