8.7 – Вывод типов для объектов с использованием ключевого слова auto

Добавлено 16 мая 2021 в 13:57
Последнее редактирование 20 июня 2021 в 11:38

В этом простом определении переменной скрывается небольшая избыточность:

double d{ 5.0 };

Поскольку C++ – язык со строгой типизацией, от нас требуется указывать явный тип для всех объектов. Таким образом, мы указали, что переменная d имеет тип double.

Однако литеральное значение 5.0, используемое для инициализации d, также имеет тип double (неявно определяется через формат литерала).

Связанный контент


Мы обсуждали, как определяются типы литералов в уроке «4.13 – Литералы».

В случаях, когда мы хотим, чтобы переменная и ее инициализатор имели один и тот же тип, мы фактически предоставляем информацию одного и того же типа дважды.

Вывод типа для инициализированных переменных

Вывод типа (или англоязычные термины type inference и type deduction) – это функция, которая позволяет компилятору определять тип объекта из его инициализатора. Чтобы использовать вывод типа, вместо типа переменной используется ключевое слово auto:

int main()
{
    auto d{ 5.0 };   // 5.0 - это литерал double, поэтому d будет иметь тип double
    auto i{ 1 + 2 }; // 1 + 2 вычисляется как int, поэтому i будет типа int
    auto x { i };    // i - это int, поэтому x тоже будет типа int
 
    return 0;
}

В первом случае, поскольку 5.0 – это литерал double, компилятор определит, что переменная d должна иметь тип double. Во втором случае выражение 1 + 2 дает результат int, поэтому переменная i будет иметь тип int. В третьем случае ранее предполагалось, что i имеет тип int, поэтому x также будет выведен как имеющий тип int.

Поскольку вызовы функций являются допустимыми выражениями, мы можем использовать вывод типа, даже когда наш инициализатор является вызовом функции:

int add(int x, int y)
{
    return x + y;
}
 
int main()
{
    auto sum { add(5, 6) }; // add() возвращает int, поэтому тип sum будет выведен как int
    return 0;
}

Функция add() возвращает значение типа int, поэтому компилятор определит, что переменная sum должна иметь тип int.

Вывод типа не будет работать для объектов, у которых нет инициализаторов, или у которых пустые инициализаторы. Таким образом, следующее недопустимо:

int main()
{
    auto x;    // Компилятор не может определить тип x
    auto y{ }; // Компилятор не может определить тип y
 
    return 0;
}

Хотя использование auto вместо базовых типов данных позволяет сэкономить лишь несколько нажатий клавиш (если экономия вообще будет), в будущих уроках мы увидим примеры, когда типы становятся сложными и длинными (а в некоторых случаях их может быть трудно понять). В таких случаях использование auto может сэкономить много времени при вводе текста (и избежать опечаток).

Вывод типа отбрасывает квалификаторы const

В большинстве случаев вывод типа удаляет квалификатор const из выводимых типов. Например:

int main()
{
    const int x { 5 }; // x имеет тип const int
    auto y { x };      // y будет типа int (const отбрасывается)
}

В приведенном выше примере x имеет тип const int, но при выводе типа для переменной y с использованием x в качестве инициализатора вывод типа выводит тип как int, а не const int.

Если вы хотите, чтобы выведенный тип был константным, вы можете использовать ключевое слово const вместе с ключевым словом auto:

int main()
{
    const int x { 5 };  // x имеет тип const int
    const auto y { x }; // y будет типа const int
}

В этом примере тип, выведенный из x, будет int (константность отброшена), но поскольку мы добавили квалификатор const во время определения переменной y, переменная y будет const int.

Для продвинутых читателей


Вывод типа не удаляет квалификатор const для указателей на константные значения, такие как типы, выведенные из строковых литералов в стиле C.

Выведение типа отбрасывает ссылки

Для продвинутых читателей

Мы еще не рассмотрели ссылки, но определение типа также отбрасывает ссылки.

Например, если вы используете вывод типа с инициализатором типа int&, выводимым типом будет int, а не int&.

int x{ 5 };  // x - обычный int
int& y{ x }; // y - ссылка int&
auto z{ y }; // z будет "int", а не "int&", потому что ссылки отбрасываются

Вы можете сделать так, чтобы выведенный тип был ссылочным, используя auto& вместо auto.

int x{ 5 };   // x - обычный int
auto& y{ x }; // выведенный тип - "int", но мы предоставили &,
              // поэтому y будет ссылкой "int&"

Вы также можете вывести константную ссылку, используя const auto&

Преимущества и недостатки вывода типа

Вывод типа не только удобен, но и имеет ряд других преимуществ.

Во-первых, если две или более переменных определены в соседних строках, имена переменных будут выровнены, что поможет повысить удобочитаемость:

// труднее читать
int a { 5 };
double b { 6.7 };
 
// легче читать
auto c { 5 };
auto d { 6.7 };

Во-вторых, вывод типа работает только с переменными, у которых есть инициализаторы, поэтому, если вы привыкли использовать вывод типа, он может помочь избежать непреднамеренно неинициализированных переменных:

int x;  // упс, мы забыли инициализировать x, но компилятор может не пожаловаться
auto y; // компилятор выдаст ошибку, потому что он не может определить тип для y

В-третьих, вам гарантировано отсутствие непреднамеренных преобразований, влияющих на производительность:

// плохо: неявно преобразует 5 из int в double
double x { 5 }; 

// хорошо: y - это число int (надеюсь, это то, что вы хотели),
// и преобразования не происходит
auto y { 5 }; 

У вывода типа также есть несколько недостатков.

Во-первых, вывод типа скрывает информацию о типе объекта в коде. Хотя хорошая IDE должна иметь возможность показать вам выведенный тип (например, при наведении курсора на переменную), всё же при использовании вывода типа немного легче делать ошибки, связанные с типами.

Например:

auto y { 5 }; // ой, нам здесь нужен double, но мы случайно предоставили литерал int

В приведенном выше коде, если бы мы явно указали y как типа double, y был бы double, даже если мы случайно предоставили инициализатор литералом int. При выводе типа y будет иметь тип int.

Вот еще один пример:

#include <iostream>
 
int main()
{
     auto x { 3 };
     auto y { 2 };
 
     std::cout << x / y; // упс, мы хотели здесь деление с плавающей запятой
 
     return 0;
}

В этом примере менее очевидно, что мы получаем целочисленное деление, а не деление с плавающей запятой.

Во-вторых, если тип инициализатора изменится, тип переменной, использующей вывод типа, также изменится, возможно, неожиданно. Рассмотрим код:

auto sum { add(5, 6) + gravity };

Если тип возвращаемого значения add изменится с int на double, или gravity изменится с int на double, sum также изменит тип с int на double.

Для продвинутых читателей


В-третьих, поскольку при выводе типов ссылки отбрасываются, если вы используете "auto" вместо "auto&", ваш код может не работать так же хорошо или даже работать некорректно.

В целом, современный консенсус состоит в том, что вывод типов, как правило, безопасно использовать для объектов, и что это может помочь сделать ваш код более читаемым за счет уменьшения акцента на информации о типе, в результате чего логика вашего кода будет выделяться лучше.

Лучшая практика


Используйте вывод типа для переменных, если вам не нужно фиксировать конкретный тип.

Примечание автора


В будущих уроках, когда нам покажется, что отображение информации о типах полезно для понимания концепции или примера, мы продолжим использовать явные типы вместо вывода типов.

Теги

autoC++ / CppLearnCppВывод типа / Type inferenceДля начинающихОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.