Кажется, что auto было довольно важной функцией, которую нужно было добавить в C ++ 11, которая, похоже, следует за многими новыми языками. Как и в случае с таким языком, как Python, я не видел явного объявления переменных (я не уверен, возможно ли это с использованием стандартов Python).

Есть ли недостаток в использовании auto для объявления переменных вместо их явного объявления?

DxAlpha

Ответов: 14

Ответы (14)

Это делает ваш код немного сложнее или утомительным для чтения. Представьте себе что-то вроде этого:

auto output = doSomethingWithData(variables);

Теперь, чтобы выяснить тип вывода, вам нужно будет отследить подпись функции doSomethingWithData.

Одним из недостатков является то, что иногда вы не можете объявить const_iterator с auto. Вы получите обычный (не константный) итератор в этом примере кода, взятого из этого вопроса:

map usa;
//...init usa
auto city_it = usa.find("New York");

В других ответах упоминаются такие недостатки, как «вы действительно не знаете, какой тип переменной». Я бы сказал, что это во многом связано с небрежным соглашением об именах в коде. Если ваши интерфейсы четко обозначены, вам не нужно заботиться о, какой именно тип. Конечно, auto result = callSomeFunction (a, b); мало что вам скажет. Но auto valid = isValid (xmlFile, schema); сообщает вам достаточно, чтобы использовать valid, не заботясь о его точном типе. В конце концов, используя только if (callSomeFunction (a, b)), вы также не узнаете тип. То же самое и с любыми другими временными объектами подвыражения. Так что я не считаю это реальным недостатком auto.

Я бы сказал, что его основным недостатком является то, что иногда точный тип возвращаемого значения не то, с чем вы хотите работать. Фактически, иногда фактический тип возврата отличается от "логический" тип возвращаемого значения как деталь реализации / оптимизации. Шаблоны выражений - яркий тому пример. Допустим, у нас есть это:

SomeType operator* (const Matrix &lhs, const Vector &rhs);

Логически мы ожидаем, что SomeType будет Vector, и мы определенно хотим рассматривать его как таковой в нашем коде. Однако возможно, что в целях оптимизации библиотека алгебры, которую мы используем, реализует шаблоны выражений, и фактический тип возвращаемого значения будет следующим:

MultExpression operator* (const Matrix &lhs, const Vector &rhs);

Теперь проблема в том, что MultExpression по всей вероятности сохранит const Matrix & и const Vector & внутри; он ожидает, что он преобразуется в Vector до конца своего полного выражения. Если у нас есть этот код, все хорошо:

extern Matrix a, b, c;
extern Vector v;

void compute()
{
  Vector res = a * (b * (c * v));
  // do something with res
}

Однако, если бы мы использовали здесь auto, у нас могли бы возникнуть проблемы:

void compute()
{
  auto res = a * (b * (c * v));
  // Oops! Now `res` is referring to temporaries (such as (c * v)) which no longer exist
}

Как этот разработчик, ненавижу auto. Или, скорее, я ненавижу, как люди злоупотребляют auto.

Я придерживаюсь (твердого) мнения, что auto предназначен для помощи в написании общего кода, не для сокращения набора текста.
C ++ - это язык, цель которого - позволить вам писать надежный код, не, чтобы минимизировать время разработки.
Это довольно очевидно из многих функций C ++, но, к сожалению, некоторые из новых, таких как auto, которые сокращают набор текста, вводят людей в заблуждение, заставляя их думать, что они должны начать лениться с набором текста.

В дни доauto люди использовали typedefs, что было здорово, потому что typedef позволял разработчику библиотеки помочь вам выяснить, что Тип возвращаемого значения должен быть таким, чтобы их библиотека работала должным образом. Когда вы используете auto, вы забираете этот элемент управления у конструктора класса и вместо этого просите компилятор выяснить, каким должен быть тип, который удаляет один из самые мощные инструменты C ++ из набора инструментов и риски взлома их кода.

Обычно, если вы используете auto, это должно быть потому, что ваш код работает для любого разумного типа, не, потому что вам просто лень записывать тип, с которым он должен работать. Если вы используете auto как инструмент, помогающий избавиться от лени, то в конечном итоге вы начинаете вносить малозаметные ошибки в свою программу, обычно вызванные неявными преобразованиями, которые не произошли из-за того, что вы использовалиавто.

К сожалению, эти ошибки трудно проиллюстрировать на коротком примере здесь, потому что их краткость делает их менее убедительными, чем реальные примеры, которые возникают в пользовательском проекте - однако они легко возникают в шаблоне. тяжелый код, который ожидает определенных неявных преобразований.

Если вам нужен пример, то здесь . Однако небольшое замечание: прежде чем поддаться искушению вскочить и раскритиковать код: имейте в виду, что многие хорошо известные и зрелые библиотеки были разработаны для таких неявных преобразований, и они существуют, потому что они решают проблемы, которое может быть трудно, если вообще возможно, решить иначе. Попытайтесь придумать лучшее решение, прежде чем критиковать их.

Как я описал в , этот ответ auto может иногда приводить к неприятным ситуациям, которые вы не планировали. Вы должны явно сказать auto &, чтобы иметь ссылочный тип, в то время как просто auto может создать тип указателя. Это может привести к путанице, если спецификатор будет полностью опущен, что приведет к созданию копии ссылки вместо фактической ссылки.

Одна из причин, о которой я могу думать, состоит в том, что вы теряете возможность принудить возвращаемый класс. Если ваша функция или метод вернули длинное 64-битное значение, а вам нужно было только 32 беззнакового int, вы теряете возможность контролировать это.

auto не имеет недостатков per se, и я рекомендую (вручную) использовать его везде в новом коде. Это позволяет вашему коду постоянно проверять типы и последовательно избегать тихой нарезки. (Если B происходит от A и функция, возвращающая A, внезапно возвращает B, тогда auto ведет себя так, как ожидалось для хранения возвращаемого значения)

Хотя унаследованный код до C ++ 11 может полагаться на неявные преобразования, вызванные использованием явно типизированных переменных.Изменение явно типизированной переменной на auto может изменить поведение кода, поэтому будьте осторожны.

Ключевое слово auto просто вывести тип из возвращаемого значения. Следовательно, он не эквивалентен объекту Python, например,

.
# Python
a
a = 10       # OK
a = "10"     # OK
a = ClassA() # OK

// C++
auto a;      // Unable to deduce variable a
auto a = 10; // OK
a = "10";    // Value of const char* can't be assigned to int
a = ClassA{} // Value of ClassA can't be assigned to int
a = 10.0;    // OK, implicit casting warning

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

Это не является недостатком auto в принципиальном смысле, но на практике это кажется проблемой для некоторых. По сути, некоторые люди либо: а) рассматривают auto как спаситель для типов и отключают свой мозг при его использовании, либо б) забывают, что auto всегда выводит типы значений. Это заставляет людей делать такие вещи:

auto x = my_obj.method_that_returns_reference();

Ой, мы только что глубоко скопировали какой-то объект. Часто это либо ошибка, либо сбой в работе. Тогда можно и в другую сторону:

const auto& stuff = *func_that_returns_unique_ptr();

Теперь у вас есть висячая ссылка. Эти проблемы вовсе не вызваны auto, поэтому я не считаю их законными аргументами против него. Но похоже, что auto делает эту проблему более распространенной (из моего личного опыта) по причинам, которые я перечислил в начале.

• 100001 Но на это нужно время.

Еще один раздражающий пример:

for (auto i = 0; i < s.size(); ++i)

генерирует предупреждение (сравнение целочисленных выражений со знаком и без знака [-Wsign-compare]), потому что i - это целое число со знаком. Чтобы этого не произошло, нужно написать, например,

for (auto i = 0U; i < s.size(); ++i)

или лучше:

for (auto i = 0ULL; i < s.size(); ++i)

Я удивлен, что никто об этом не упомянул, но предположим, что вы вычисляете факториал чего-то:

# включить 
используя пространство имен std;

int main () {
    авто n = 40;
    авто факториал = 1;

    для (int i = 1; i <= n; ++ i)
    {
        факториал * = i;
    }

    cout << "Факториал" << n << "=" << факториал << endl;
    cout << "Размер факториала:" << sizeof (факториал) << endl;
    возврат 0;
}

Этот код выведет это:

Factorial of 40 = 0
Size of factorial: 4

Это был явно не ожидаемый результат. Это произошло потому, что auto вывел тип переменной factorial как int, потому что он был назначен на 1.

Вы спрашивали только о недостатках, поэтому я выделю некоторые из них. При правильном использовании auto также имеет ряд преимуществ. Недостатки связаны с простотой злоупотреблений и повышенным потенциалом непреднамеренного поведения кода.

Главный недостаток состоит в том, что, используя auto, вы не обязательно знаете тип создаваемого объекта. Бывают также случаи, когда программист может ожидать, что компилятор выведет один тип, но компилятор непреклонно выведет другой.

Учитывая объявление типа

auto result = CallSomeFunction(x,y,z);

вам необязательно знать, какой тип результат. Это может быть int. Это может быть указатель. Может быть что-то еще. Все они поддерживают разные операции. Вы также можете кардинально изменить код, внося незначительные изменения, например

.
auto result = CallSomeFunction(a,y,z);

, потому что, в зависимости от того, какие перегрузки существуют для CallSomeFunction (), тип результата может быть совершенно другим - и, следовательно, последующий код может вести себя совершенно иначе, чем предполагалось. Вы можете внезапно вызвать сообщения об ошибках в более позднем коде (например, впоследствии попытаться разыменовать int, пытаясь изменить что-то, что сейчас const). Более зловещее изменение заключается в том, что ваше изменение проходит мимо компилятора, но последующий код ведет себя по-разному и неизвестно - возможно, с ошибками.

Отсутствие явного знания типа некоторых переменных поэтому затрудняет строгое обоснование утверждения о том, что код работает так, как задумано. Это означает больше усилий для обоснования заявлений о «пригодности для использования» в областях с высокой критичностью (например, критичных для безопасности или критически важных).

Другой, более распространенный недостаток - это соблазн программиста использовать auto в качестве грубого инструмента для принудительной компиляции кода вместо того, чтобы думать о том, что делает код, и работать над его получением. вправо.

То, о чем здесь пока никто не упоминал, но само по себе заслуживает ответа, если вы спросите меня.

Поскольку (даже если все должны знать, что C! = C ++) код, написанный на C, можно легко спроектировать для обеспечения основы для кода C ++ и, следовательно, разрабатывать без особых усилий, чтобы он был совместим с C ++. , это может быть требованием для проектирования.

Я знаю о некоторых правилах, в которых некоторые четко определенные конструкции из C недействительны для C ++ и наоборот. Но это просто приведет к неработоспособности исполняемых файлов, и применяется известное предложение UB, которое в большинстве случаев замечено странными циклами, приводящими к сбоям или чему-то еще (или даже может остаться незамеченным, но здесь это не имеет значения).

Но auto это первый раз1 это меняет!

Представьте, что вы использовали auto в качестве спецификатора класса хранения раньше и передаете код. Это даже не обязательно (в зависимости от способа использования) «ломается»; на самом деле он мог незаметно изменить поведение программы.

Об этом нужно помнить.


1По крайней мере, впервые мне известно.

Я думаю, что auto хорош, когда используется в локализованном контексте, где читатель легко и очевидно может определить его тип, или хорошо документирован с комментарием его типа или именем, которое выводит фактический тип. Те, кто не понимает, как это работает, могут ошибиться, например, использовать его вместо шаблона или подобного. На мой взгляд, вот несколько хороших и плохих вариантов использования.

void test (const int & a)
{
    // b is not const
    // b is not a reference

    auto b = a;

    // b type is decided by the compiler based on value of a
    // a is int
}

Хорошее использование

Iterators

std::vector,int> v();

..

std::vector,int>::iterator it = v.begin();

// VS

auto vi = v.begin();

Указатели функций

int test (ClassWithLongName1 a, ClassWithLongName2 b, int c)
{
    ..
}

..

int (*fp)(ClassWithLongName1, ClassWithLongName2, int) = test;

// VS

auto *f = test;

Неправильное использование

Поток данных

auto input = "";

..

auto output = test(input);

Подпись функции

auto test (auto a, auto b, auto c)
{
    ..
}

Банальные случаи

for(auto i = 0; i < 100; i++)
{
    ..
}

2022 WebDevInsider