10.8 — Wpisz dedukcję dla obiektów za pomocą auto słowo kluczowe

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ą auto słowa kluczowego, które można zastosować zamiast typu zmiennej:

int main()
{
    auto d { 5.0 }; // 5.0 is a double literal, so d will be deduced as a double
    auto i { 1 + 2 }; // 1 + 2 evaluates to an int, so i will be deduced as an int
    auto x { i }; // i is an int, so x will be deduced as an 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() returns an int, so sum's type will be deduced as an 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 }; // f suffix causes a to be deduced to float
    auto b { 5u };    // u suffix causes b to be deduced to 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 is an int

    const auto b { 5 };     // b is a const int
    constexpr auto c { 5 }; // c is a 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;           // The compiler is unable to deduce the type of a
    auto b { };       // The compiler is unable to deduce the type of b
    auto c { foo() }; // Invalid: c can't have type incomplete type 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 has type 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 has type const int
    const auto b { a }; // b has type const int (const dropped but reapplied)


    return 0;
}

W tym przykładzie typ wydedukowany z a będzie int (the 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 will be type const char*, not std::string

Jeś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; // easiest way to access the s and sv suffixes

    auto s1 { "goo"s };  // "goo"s is a std::string literal, so s1 will be deduced as a std::string
    auto s2 { "moo"sv }; // "moo"sv is a std::string_view literal, so s2 will be deduced as a 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 has type const double (constexpr not part of type, const is implicit)

    auto b { a };                // b has type double (const dropped)
    const auto c { a };          // c has type const double (const dropped but reapplied)
    constexpr auto d { a };      // d has type const double (const dropped but implicitly reapplied by 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ść:

// harder to read
int a { 5 };
double b { 6.7 };

// easier to read
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; // oops, we forgot to initialize x, but the compiler may not complain
auto y; // the compiler will error out because it can't deduce a type for y

Po trzecie, masz gwarancję, że nie będzie żadnych niezamierzonych konwersji wpływających na wydajność:

std::string_view getString();   // some function that returns a std::string_view

std::string s1 { getString() }; // bad: expensive conversion from std::string_view to std::string (assuming you didn't want this)
auto s2 { getString() };        // good: no conversion required

Dedukcja 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 }; // oops, we wanted a double here but we accidentally provided an int literal

W 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'; // oops, we wanted floating point division here

     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.

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