Новое ключевое слово "авто"; Когда он должен использоваться для объявления типа переменной?

Возможный дубликат:
Сколько стоит слишком много с ключевым словом С++ 0x auto

Имеем ли мы (как сообщество) достаточно опыта, чтобы определить, когда и/или подвергается ли злоупотребление авто?

То, что я действительно ищу, - это руководство по лучшей практике на

  • когда использовать auto
  • когда этого следует избегать

Простые эмпирические правила, которые можно быстро выполнить в 80% случаев.

В качестве контекста этот вопрос вызван моим ответом здесь

Ответ 1

Я думаю, когда тип очень хорошо известен среди со-программистов, которые работают (или будут работать) в вашем проекте, тогда можно использовать auto, например, в следующем коде:

//good : auto increases readability here
for(auto it = v.begin(); it != v.end(); ++it) //v is some [std] container
{
      //..
}

Или, в более общем плане,

//good : auto increases readability here
for(auto it = std::begin(v); it != std::end(v); ++it)//v could be array as well
{
      //..
}

Но когда тип не очень хорошо известен и редко используется, то я думаю, что auto отображение уменьшает читаемость, например, здесь:

//bad : auto decreases readability here
auto obj = ProcessData(someVariables);

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


Другое место, где можно использовать auto, - это когда вы используете new 1 или make_*, например здесь:

//without auto. Not that good, looks cumbersome
SomeType<OtherType>::SomeOtherType * obj1 = new SomeType<OtherType>::SomeOtherType();
std::shared_ptr<XyzType> obj2 = std::make_shared<XyzType>(args...);
std::unique_ptr<XyzType> obj2 = std::make_unique<XyzType>(args...);

//With auto. good : auto increases readability here
auto obj1 = new SomeType<OtherType>::SomeOtherType();
auto obj2 = std::make_shared<XyzType>(args...);
auto obj3 = std::make_unique<XyzType>(args...);

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

1. Избегайте использования new и необработанных указателей.


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

foam::composition::expression<int> x;

auto s = x * x;       //square
auto c = x * x * x;   //cube
for(int i = 0; i < 5 ; i++ )
    std::cout << s(i) << ", " << c(i) << std::endl; 

Вывод:

0, 0
1, 1
4, 8
9, 27
16, 64

Теперь сравните приведенный выше код со следующим эквивалентным кодом, который не использует auto:

foam::composition::expression<int> x;

//scroll horizontally to see the complete type!!
foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply>> s = x * x; //square
foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply> >, foam::composition::expression<int>, foam::operators::multiply>> c = x * x * x; //cube

for(int i = 0; i < 5 ; i++ )
    std::cout << s(i) << ", " << c(i) << std::endl; 

Как вы можете видеть, в таких случаях auto делает вашу жизнь экспоненциально проще. Выражения, используемые выше, очень просты; подумайте о типе более сложных выражений:

auto a = x * x - 4 * x + 4; 
auto b = x * (x + 10) / ( x * x+ 12 );
auto c = (x ^ 4 + x ^ 3 + x ^ 2 + x + 100 ) / ( x ^ 2 + 10 );

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


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

Ответ 2

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

for (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++(), он вроде как утка, печатая в этом отношении.

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

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

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

Ответ 3

Id применяет то же правило, что и для var в С#: использовать его свободно. Это повышает читаемость. Если тип переменной на самом деле достаточно важен, чтобы ее можно было явно указать, в каких случаях это должно быть сделано (duh).

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

Кроме того, auto может фактически предотвращать ошибки, предотвращая нежелательные неявные преобразования при инициализации. Как правило, оператор Foo x = y; будет выполнять неявное преобразование, если y isnt типа Foo и существует неявное преобразование. Это причина того, что во избежание неявных преобразований в первую очередь. К сожалению, у С++ их слишком много.

Написание auto x = y; предотвратит эту проблему в принципе.

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

Не все случаи столь же четкие, но я утверждаю, что большинство из них, и что

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

Эрик Липперт, главный разработчик команды компилятора С#, сказал то же самое с относится к var.

Ответ 4

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

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

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

Еще один пример: когда вам нужно убедиться, что конкретная переменная имеет определенный тип для чего-то вроде внешнего интерфейса. Например, рассмотрим применение маски сети к IP-адресу (v4). Для аргумента предположим, что вы работаете с отдельными октетами адреса (например, представляя каждый как unsigned char), поэтому мы получаем что-то вроде octets[0] & mask[0]. Благодаря правилам повышения типа C, даже если оба операнда unsigned char s, результат обычно будет int. Нам нужен результат unsigned char хотя (то есть один октет), а не int (обычно 4 октета). Таким образом, в этой ситуации auto почти наверняка будет неуместным.

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

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

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

Ответ 5

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

Ответ 6

Я использовал языки с полным типом вывода. Я не вижу причин не ставить auto везде, где это технически возможно *. На самом деле я, возможно, уже написал auto i = 0;, где int - один символ короче auto. Я даже не уверен, что сделал, потому что нижняя часть: я не забочусь о манифестах.

*: например auto int[] = { 0, 1, 2, 3 } не работает.