Сколько это слишком много с ключевым словом auto C ++ 11?

218

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

auto foo = std::make_shared<Foo>();

И более скептически для:

auto foo = bla(); // where bla() return a shared_ptr<Foo>

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

Чтобы уточнить: я не спрашиваю философское мнение; Я спрашиваю о предполагаемом использовании этого ключевого слова стандартным комитетом, возможно, с комментариями о том, как это предполагаемое использование реализуется на практике.

Примечание: этот вопрос был перемещен в SE.Programmers, а затем обратно в переполнение стека. Обсуждение этого можно найти в этом мета-вопросе .

Алан Тьюринг
источник
Это сайт вопросов и ответов, а не дискуссионный сайт. Вы задали очень очень общий вопрос, и я сомневаюсь, что кто-нибудь сможет дать вам что-то кроме очень субъективного. (вот почему -1)
TravisG
15
@heishe, я добавил пояснения. Если вы читаете вопрос очень широко, кажется, что он задает субъективное мнение, но на самом деле, если вы использовали autoключевое слово, то вы знаете, как оно должно использоваться. Это то, что я спрашиваю, как кто-то новичок в этой функции, как я должен ее использовать?
Алан Тьюринг
13
Я видел это обсуждение повсюду, когда появился C # var(то есть, когда люди перестали думать, что это не динамическая типизация, в конце концов). Если вы хотите, вы можете начать с этого вопроса и перейти к соответствующим вопросам.
Р. Мартиньо Фернандес
2
@Lex: Либо что-то законно, либо нет; называть что-то «плохое» законным субъективно по определению. То есть, называть auto foo = bla();«плохим» - это, несомненно, мнение, а не факт, который ставит этот вопрос и отвечает на обсуждение, что делает его актуальным для программистов SE, что и есть то, что указывают голоса при закрытии. /
пожав
3
Взгляд Херба Саттера на этот вопрос: herbutter.com/2013/06/13/…
rafak

Ответы:

131

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

my_multi_type::nth_index<2>::type::key_type::composite_key_type::
    key_extractor_tuple::tail_type::head_type::result_type

чтобы ввести составной ключ boost::multi_index, даже если вы знаете, что это так int. Вы не можете просто написать, intпотому что это может быть изменено в будущем. Я бы написал autoв этом случае.

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

Вот некоторые примеры:

auto foo = std::make_shared<Foo>();   // obvious
auto foo = bla();                     // unclear. don't know which type `foo` has

const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
                                      // since max_size is unsigned

std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it )
                                      // ok, since I know that `it` has an iterator type
                                      // (don't really care which one in this context)
Кирилл В. Лядвинский
источник
18
Это не похоже на очень хорошую рекомендацию. Как часто вы не знаете тип объекта? (То есть за пределами шаблонов.) И если вы не знаете, что они печатают, посмотрите, не ленитесь и используйте auto.
Пол Манта
9
@Paul: часто вы знаете только или должны знать только наиболее важную часть типа, например, что это итератор, но вы не знаете и не заботитесь, является ли он итератором векторов или итератором связанного список; в этих случаях вы действительно не хотите тратить время и место на экране, пытаясь понять, как записывать типы.
Ли Райан
45
Нравится ли это фанатам C ++ или нет, C ++ 0x привлечет людей, которые никогда не использовали бы C ++. И те будут использовать авто повсюду.
Профессор Фалькен
6
@ R.MartinhoFernandes - нет, единственное, что ясно, это то, что bla() вы отдаете все , что возвращаете foo.
Луис Мачука
8
@LuisMachuca мой ответ был ненормативным способом сказать, что нечестно приводить в учебнике пример неверного именования переменных и функций и обвинять его в недостаточной читаемости при выводе типа.
Р. Мартиньо Фернандес
62

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

Джон Перди
источник
12
Я бы сказал особенно «const auto &»
Виктор Сер
2
Лучше использовать auto&&в сложных ситуациях edmundv.home.xs4all.nl/blog/2014/01/28/…
KindDragon
2
@ KindDragon: Это хорошее эмпирическое правило, но я предпочитаю использовать const auto&или, const autoесли я не хочу явно изменить или переместить.
Джон Пурди
Msgstr " Используйте авто везде, где сможете ". Значит ли это, что я должен писать auto str = std::string();вместо std::string str;?
Кальмариус
4
Повсеместное использование auto сделает ваш код менее читаемым и сложным для отладки, потому что вам придется самостоятельно определять типы при чтении кода.
SubMachine
52

Легко. Используйте его, когда вам все равно, какой тип. Например

for (const auto & i : some_container) {
   ...

Все, что меня волнует, это то, что iнаходится в контейнере.

Это немного похоже на typedefs.

typedef float Height;
typedef double Weight;
//....
Height h;
Weight w;

Здесь, я не забочусь ли hи wявляются поплавки или двойники, только то , что они независимо от типа подходит для выражения высоты и веса .

Или посмотрим

for (auto i = some_container .begin (); ...

Здесь все, что меня волнует, это то, что это подходящий итератор, поддерживающий operator++(), это похоже на типизацию утки в этом отношении.

Кроме того, тип лямбда не может быть написано, так auto f = []...что хороший стиль. Альтернатива - приведение к, std::functionно это идет с накладными расходами.

Я не могу себе представить "злоупотребление" auto. Самое близкое, что я могу себе представить, - это лишить себя явного преобразования в какой-то значимый тип - но вы бы не использовали autoдля этого, вы бы создали объект нужного типа.

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

Контрпримеры (заимствовано из чужих ответов):

auto i = SomeClass();
for (auto x = make_unsigned (y); ...)

Здесь нас интересует, что это за тип, поэтому мы должны написать Someclass i;иfor(unsigned x = y;...

spraff
источник
1
Um. Не так просто. Он компилируется и запускается, и вы просто стреляете себе в ногу, если элементы являются нетривиальными объектами - ваша итерация вызывает конструктор копирования и деструктор на каждом этапе итерации. Если вы собираетесь вслепую использовать auto в итераторе на основе диапазона, вероятно, оно должно быть «for (const auto & item: some_container)» вместо «for (auto item: some_container)».
Дон Хэтч
2
Не всегда, но хорошо, вы, вероятно, хотите ссылки. Ну и что? Это не имеет ничего общего с auto.
spraff 25.10.14
Я действительно не понимаю ваш последний комментарий. Я пытался объяснить, почему ваша стратегия не выглядит для меня очень хорошей и почему я ее опровергаю.
Дон Хэтч
5
Я говорю, что, используете ли вы ссылки или нет, это ортогонально тому, используете ли вы авто или нет. Я отредактирую это, чтобы добавить ссылку, потому что по общему признанию это обычно - то, что каждый хочет сделать, но это абсолютно не имеет отношения к теме под рукой.
spraff
43

Действуй. Используйте autoвезде, где это облегчает написание кода.

Каждая новая функция на любом языке будет перегружена, по крайней мере, некоторыми типами программистов. Только через умеренное злоупотребление некоторыми опытными программистами (не нубами) остальные опытные программисты узнают границы правильного использования. Чрезмерное чрезмерное использование обычно плохо, но может быть хорошим, потому что такое чрезмерное использование может привести к улучшению функции или улучшению возможности ее замены.

Но если бы я работал над кодом с несколькими строками вроде

auto foo = bla();

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

DarenW
источник
38

На C ++ и после 2012 года на панели « Спроси нас» состоялся фантастический обмен мнениями между Андреем Александреску, Скоттом Мейерсом и Хербом Саттером, который говорил о том, когда использовать и не использоватьauto . Перейдите к минуте 25:03 для 4-минутного обсуждения. Все три динамика дают отличные оценки, о которых следует помнить, когда их не использовать auto.

Я настоятельно призываю людей прийти к собственному выводу, но я забрал, чтобы использовать autoвезде, если :

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

Либеральное использование explicitпомогает уменьшить беспокойство о последнем, что помогает минимизировать количество времени, в течение которого первое является проблемой.

Перефразируя сказанное Хербом: «Если вы не делаете X, Y и Z, используйте auto. Изучите, что такое X, Y и Z, и иди и используйте autoвезде».

Шон
источник
4
Вероятно, также стоит сделать ссылку
Роб Старлинг
37

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

   for(auto i = container.begin(); i != container.end(); ++i);

auto здесь не повредит читабельности.

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

   auto spaces = space & space & space;

с участием

r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&> spaces = 
   space & space & space;

С другой стороны, когда тип известен и прост, гораздо лучше, если он явно указан:

int i = foo();

скорее, чем

auto i = foo();
Джин Бушуев
источник
2
Конечно, наличие в языке цикла for на основе диапазона делает ваш первый пример менее интересным. 8v)
Фред Ларсон
@Fred: тип все еще может быть громоздким (я думаю, ассоциативные контейнеры)
Матье М.
3
@Fred: Каждый раз , когда ваши границы не begin()и end(), или ваш размер шага ничего, кроме одного, или вы изменяете контейнер , как вы петли, диапазон на основе для заявления не поможет.
Деннис Зикефуз
6
@geotavros: И r_and_t<r_and_t<r_char_t<char>&, r_char_t<char>&>, r_char_t<char>&>делает?
Деннис Зикефуз
7
@geotavros: Или вы можете увидеть, какой тип space, и искать его. В любом случае, это более полезная информация ... в конце концов, проблема не в том, «какого типа эта новая переменная», а в том, «что это space & space & spaceзначит?». Фактический тип выражения - просто шум.
Деннис Зикефуз
19

auto может быть очень опасным в сочетании с шаблонами выражений, которые часто используются библиотеками линейной алгебры, такими как Eigen или OpenCV.

auto A = Matrix(...);
auto B = Matrix(...);
auto C = A * B; // C is not a matrix. It is a matrix EXPRESSION.
cout << C; // The expression is evaluated and gives the expected result.
... // <code modifying A or B>
cout << C; // The expression is evaluated AGAIN and gives a DIFFERENT result.

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

auto C = Matrix(A * B); // The expression is now evaluated immediately.
morotspaj
источник
1
Это кажется странным поведением для начала. Если я умножаю две матрицы, даже если операция ленивая, я не ожидал бы, что она будет переоценена, я ожидаю, что она сохранит свое оцененное состояние после первоначальной оценки. Если вы хотите изменить параметры без изменения исходных параметров, не придется ли вам в любом случае перестраивать выражение? Или он предназначен для ситуации потоковой обработки, где исходные параметры постоянно меняются, но процедура остается неизменной?
JAB
1
Независимо от того, A*Bскопировано ли выражение в autoпеременную или что-то еще, описанное вами поведение все еще присутствует.
xtofl
Нет ошибок вызвано использованием auto.
Джим Балтер
@JAB: он разработан для поддержки упрощений и синергизма между операциями, например diag(A * B), не нужно тратить циклы на вычисление недиагональных элементов.
Бен Фойгт
Я также заметил такой код, как «for (auto o: vecContainer)», где «o» был тяжелым объектом, который копируется каждый раз. Я рекомендую использовать его для того, что изначально было задумано, а именно для шаблонов (со сложными правилами вывода) и трудоемких типов определения. Даже в этом случае вы должны быть осторожны и различать авто и авто &.
gast128
10

Я использую autoбез ограничений и не столкнулся с какой-либо проблемой. Я даже иногда заканчиваю тем, что использую это для простых типов как int. Это делает c ++ языком более высокого уровня для меня и позволяет объявлять переменные в c ++, как в python. После написания кода на Python я даже иногда пишу, например,

auto i = MyClass();

вместо того

MyClass i;

Это один из случаев, когда я бы сказал, что это злоупотребление autoключевым словом.

Часто я не возражаю против того, каков точный тип объекта, меня больше интересует его функциональность, и поскольку имена функций обычно говорят что-то об объектах, которые они возвращают, autoэто не повредит: например auto s = mycollection.size(), я могу догадаться, что sэто будет своего рода целое число, и в редком случае, когда я забочусь о точном типе, давайте проверим прототип функции (я имею в виду, что я предпочитаю проверять, когда мне нужна информация, а не априори, когда пишется код, просто в случае, если это будет полезно когда-нибудь, как в int_type s = mycollection.size()).

По поводу этого примера из принятого ответа:

for ( auto x = max_size; x > 0; --x )

В моем коде я все еще использую autoв этом случае, и если я хочу xбыть беззнаковым, то я использую служебную функцию с именем say make_unsigned, которая ясно выражает мои опасения:

for ( auto x = make_unsigned(max_size); x > 0; --x )

Отказ от ответственности: я просто описываю свое использование, я не компетентен давать советы!

rafak
источник
1
@ChristianRau: не был саркастичным. Обратите внимание, что я не рекомендовал использовать auto i = MyClass().
Рафак
3
Последующие действия: см .: herbutter.com/2013/06/13/… . Использование например as_unsignedрекомендуется там, или даже auto w = widget{};.
Рафак
3

Одна из основных проблем с программой на C ++ - она ​​позволяет использовать неинициализированную переменную . Это приводит нас к неприятному недетерминированному поведению программы. Следует отметить, что современный компилятор теперь выдает соответствующие предупреждающие сообщения / сообщения, если программа устает его использовать.

Чтобы проиллюстрировать это, рассмотрим ниже программу на С ++:

int main() {
    int x;
    int y = 0;
    y += x;
}

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

main.cpp: в функции 'int main ()':

main.cpp: 4: 8: предупреждение : «x» используется неинициализированным в этой функции [-Wuninitialized]

у + = х;

    ^

================================================== =============================== Теперь, если мы изменим нашу программу, которая использует auto , то после компиляции мы получим следующее:

int main() {
    auto x;
    auto y = 0;
    y += x;
}

main.cpp: в функции 'int main ()':

main.cpp: 2: 10: ошибка : объявление auto x не имеет инициализатора

 auto x;

      ^

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

Эту концепцию и другую великолепную современную концепцию C ++ объясняет эксперт C ++ Херб Шуттер в своем выступлении на CppCon14 :

Вернуться к основам! Основы современного стиля C ++

Мантош Кумар
источник
2
Да. И чтобы указать тип, вы можете инициализировать как 0i, 0u, 0l, 0ul, 0.0f, 0.0 или даже int (), unsigned (), double () и т. Д.
Робинсон
6
Этот аргумент смешной. Вы говорите: «Вы не можете забыть инициализатор, потому что вы получите ошибку компилятора», но это требует, чтобы вы не забыли использовать auto.
Гонки легкости на орбите
1
@LightnessRacesinOrbit: Я понял (изучил) эту концепцию из выступления Херба Саттера. Я нашел логичный / практический совет от разговоров о травах, поэтому подумал поделиться с сообществом.
Мантош Кумар
2
Я не думаю, что это хорошая мотивация для использования авто. Просто включите флаг предупреждения вашего компилятора для использования неинициализированных переменных и обращайтесь ко всем предупреждениям. Это не значит, что вы не должны использовать auto- но вам это не нужно, чтобы избежать этой проблемы.
einpoklum
2

Одна опасность, которую я отметил, заключается в ссылках. например

MyBigObject& ref_to_big_object= big_object;
auto another_ref = ref_to_big_object; // ?

Проблема в том, что another_ref на самом деле не является ссылкой в ​​этом случае, это MyBigObject вместо MyBigObject &. В итоге вы копируете большой объект, не осознавая этого.

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

auto another_ref = function_returning_ref_to_big_object();

вам понадобится "auto &" или "const auto &"

MyBigObject& ref_to_big_object= big_object;
auto& another_ref = ref_to_big_object;
const auto& yet_another_ref = function_returning_ref_to_big_object();
user2495422
источник
1

Используйте autoтам, где имеет смысл выводить тип. Если у вас есть что-то, что, как вы знаете, является целым числом, или вы знаете, что это строка, просто используйте int / std :: string и т. Д. Я бы не стал беспокоиться о «чрезмерном использовании» языковой функции, если она не дошла до абсурда, или запутывает код.

Это мое мнение в любом случае.

LainIwakura
источник
4
«где это имеет смысл ...» это сложная часть. Программисты могут настолько погрузиться в детали кодирования, что потеряют понимание того, что имеет смысл для других программистов, в частности, для будущих разработчиков.
ДаренВ
Это правда, хотя я думаю, что довольно легко сказать, когда это неоднозначно. Если сомневаетесь, используйте комментарий!
LainIwakura
1
Разве не имеет смысла использовать тип?
Михаил
Некоторые разработчики скажут, что тип должен всегда выводиться!
Арафангион
Используйте комментарий ...... или просто используйте тип, и комментарий не нужен?
user997112
1

autoКлючевое слово может использоваться только для локальной переменной, но не для аргументов или членов класса / структуры. Таким образом, это безопасно и целесообразно использовать их где угодно. Я часто их использую. Тип выводится во время компиляции, отладчик показывает тип во время отладки, sizeofсообщает об этом правильно, decltypeдает правильный тип - вреда нет. Я не считаю autoчрезмерным, никогда!

Аджай
источник
0

TL; DR: см. Практическое правило внизу.

Общепринятый ответ предлагает следующее правило:

Используйте autoвсякий раз, когда трудно сказать, как написать тип с первого взгляда, но тип правой части выражения очевиден.

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

auto x = f();

Что делает это примером неправильного использования auto? Это мое невежество о f()типе возврата? Ну, это может действительно помочь, если бы я знал это, но - это не моя главная забота. Гораздо более серьезной проблемой является то , что xи f()довольно бессмысленно. Если бы мы имели:

auto nugget = mine_gold();

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

Итак, мой ответ: используйте autoвсякий раз, когда это позволяет компилятор, если только:

  • Вы чувствуете, что имя переменной вместе с выражением инициализации / присваивания не предоставляют достаточно информации о том, что делает оператор.
  • Вы чувствуете, что имя переменной вместе с выражением инициализации / присваивания предоставляет «вводящую в заблуждение» информацию о том, каким должен быть тип, т. Е. Если бы вам пришлось угадывать, что приходит вместо auto, вы могли бы сделать предположение - и это было бы неправильно, и это ложное предположение имеет последствия позже в коде.
  • Вы хотите использовать другой тип (например, ссылку).

А также:

  • Предпочитаю давать значимое имя (которое, конечно, не содержит имени типа) перед заменой autoна конкретный тип.
einpoklum
источник
-3

Один мой горький опыт с autoкак использовать его с лямбда - выражений :

auto i = []() { return 0; };
cout<<"i = "<<i<<endl; // output: 1 !!!

Собственно, здесь iразрешено указатель на функцию int(*)(). Это просто cout, но только представьте, какие плохие ошибки компиляции / выполнения могут возникнуть при использовании с template.

Вы должны избегать auto с такими выражениями и поставить правильный returnтип (или контролируемый decltype())

Правильное использование для приведенного выше примера будет,

auto i = []() { return 0; }(); // and now i contains the result of calling the lambda  
iammilind
источник
7
Вы проделали ужасную работу, объясняя, что это имеет отношение к авто. Вы создали функцию, а затем распечатали ее ... Хорошо?
Денис Зикефуз
5
@iammilind: а какое это имеет отношение к авто?
Р. Мартиньо Фернандес
7
Я считаю крайне маловероятным, что кто-то хотел бы положить ()в конце. Лямбды должны действовать как функции, и отсюда происходит преобразование указателя на функцию. Если вы хотите позвонить прямо сейчас, зачем вообще использовать лямбду? auto i = 0;работает довольно хорошо.
Р. Мартиньо Фернандес
4
Можете ли вы хотя бы описать сценарий, в котором auto x = []() { /* .... whatever goes in here ... */ }() лучше, чем auto x = /* .... whatever goes in here ... */;то же самое, без лямбды? Я считаю, что это довольно бессмысленно, по той же причине auto x = 42 + y - 42бессмысленно.
Р. Мартиньо Фернандес
6
-1 это не вина авто Тип лямбды не может быть написан, поэтому требуется авто , если вы забудете вызвать функцию, это ваш собственный поиск! Вы можете поместить так называемый указатель на не вызываемую функцию в C printf так же случайно.
Spraff