C++0x наконец-то вышел. Кто-то безгранично рад этому и уже по полной использует его новые возможности, кому-то все равно, ну а кто-то не видит в этом ничего хорошего. Что касается меня, то я отношусь к нему с осторожностью, изучая его и пытаясь найти рациональное применение новым особенностям языка.
Вчера я смотрел доклад Герба Саттера на конференции Going Native 2012, где он рассказывал о С++11 и о том, как он меняет способ использования С++. Рассказывая о ключевом слове auto, Герб призвал использовать его по умолчанию, явно указывая тип только когда это необходимо. Бесспорно, использование auto сильно упрощает набор олимпиадного кода. Например,
map<string, pair<int, int> > m;
auto i = m.find("a");
писать гораздо проще, чем "по-старому".
Однако в больших проектах, как мне кажется, ситуация иная. В таких программах огромное значение имеет читабельность кода, простота его восприятия, а не сокращение времени его набора. Рассмотрим пример.
auto x = foo(Widget());
x.bar();
Что можно сказать, взглянув на этот код? Очевидно, функция foo()
возвращает объект некоторого класса, у которого потом вызывается метод bar()
. Для дальнейшего использования объекта x мы должны знать его класс, чтобы обращаться к его интерфейсу. Как узнать этот класс?
Ответ напрашивается сам собой — посмотреть объявление функции foo()
. Любая современная IDE подскажет вам тип возвращаемого ею значения, вам даже не придётся искать её в проекте. Однако это требует выполнения дополнительного действия, в котором не было бы необходимости при явном указании типа. К тому же, стоит отметь, что возникает зависимость от IDE, с бумажки такой код читать будет тяжелее. Пусть это и не столь критично, но всё-таки...
А ведь ситуация может оказаться ещё веселее. Рассмотрим вот такой код:
auto foo(Widget w) -> decltype(w.prop())
{
return w.prop();
}
Что мы здесь видим? Тип функции foo()
нигде явно не определён, необходимо узнать, что возвращает метод prop()
класса Widget
. А это требует дальнейшего исследования кода. В результате мы можем уйти глубоко в дебри нашего проекта, только чтобы узнать, какой тип имеет переменная x. Конечно, приведённые примеры искусственные, но мне кажется, нечто подобное запросто может появиться и в реальном коде.
Резюме. Этой статьёй я не хочу сказать, что C++11 — это плохо, и его использование не принесёт ничего кроме дополнительной головной боли. Я только хочу сказать, что использовать его возможности надо умеренно и только там, где они действительно упрощают создание и понимание кода. auto, на мой взгляд, — это очень полезная возможность C++11, однако его чрезмерное употребление может существенно усложнить понимание написанного кода. Поэтому я не согласен с Саттером и считаю, что использование auto по умолчанию — не очень хорошая идея.
Я согласен с автором. У меня возникли такие же мысли, когда я читал про использование var в C#, где оно означает то же, что auto в С++11. Вот что пишут про var:
You should use implicitly typed variable declarations sparingly. Obviously, for anonymous types, it is not possible to specify the data type, and the use of var is required. However, for cases where the data type is not an anonymous type, it is frequently preferable to use the explicit data type. As is the case generally, you should focus on making the semantics of the code more readable while at the same time using the compiler to verify that the resultant variable is of the type you expect. To accomplish this with implicitly typed local variables, use them only when the type assigned to the implicitly typed variable is entirely obvious. For example, in var items = new Dictionary<string, List>();, the resultant code is more succinct and readable. In contrast, when the type is not obvious, such as when a method return is assigned, developers should favor an explicit variable type declaration such as the following: Dictionary<string, List> dictionary = GetAccounts();
Mark Michaelis "Essential C# 4.0"
Прошу прощения за длинную цитату, но мне думается, что она вполне содержательна и применима к auto в C++.
К тому же, в ней приводится еще один аргумент в пользу спецификации типа переменной. Конкретная спецификация типа гарантирует то, что в переменной будет значение именно того типа, который имел в виду программист, когда писал код.
По поводу примера
Dictionary<string, List> dictionary = GetAccounts();
Обычно в больших проектах не рекомендуется использовать
Dictionary<string, List>
as is. Если этот Dictionary используется много раз, то разумно завести typedef на него.Также не стоит называть переменную
dictionary
-- так как это не отражает ее семантику. Я бы называл переменнуюaccounts
.После этого приведенный кусок (до С++11) будет выглядеть следующим образом
Очевидно, что от применения auto код только выиграет (потому что и так понятно о чем речь).
Мое мнение, что если придерживаться грамотного code-style (например, не называть переменную ничего не значащим именем
x
), то от auto чиаемость кода только повышается. (Так как избавляет от тавтологий и страшных крокодилов)Мне кажется, что
TAccounts accounts = GetAccounts();
ничем не хуже чемauto accounts = GetAccounts();
.Т.е. на самом деле первый вариант лучше, учитывая аргументы, приведенные в оригинальном посте.
По-моему, с читаб**Е**льностью ничего не произойдёт в том случае, если использовать auto не всюду и повально, а только в тех местах, где тип выражения можно явно зрительно вывести из окружения и это действительно резко сокращает код. Например, чтобы проитерироваться по контейнеру. Меня жутко порадовала возможность писать
for (auto y : E[x])
вместоfor (list<int>::iterator it = E[x].begin(); it != E[x].end(); it++)
без нагромождения макросов. Ясно же, что y должно иметь тип содержимого контейнера E[x]? А писатьauto x = 4 + y * z
по мне глупо.Согласен. Вопрос в том, почему тогда Саттер рекомендует использовать auto где только можно?
Надо ещё раз пересмотреть ту часть доклада, где он об этом говорит. Но насколько я помню, он не затрагивает вопросы читабельности, а просто говорит, что зачем указывать тип, если компилятор его и так знает.
Согласен, это один из примеров подходящего применения auto. Вот другие примеры, в которых его применение мне кажется уместным:
Я бы не сказал, что такое применение, как в примере с вектором, можно назвать уместным.
Это скорее пример, как с помощью auto сделать работающий код неработающим. :)
Да, согласен, пример не удался.
Я правильно понимаю, что "сделать работающий код неработающим" относится к ситуации, когда v.size() == 0, потому что в этом случае i будет проинициализировано большим положительным числом?
А также к условию i >= 0, которое всегда выполняется.
Ну да, поторопился я с этим примером. Добиться типобезопасности при итерации по вектору, видимо, можно вот так:
Но это уже не имеет отношения к auto :) И вообще, применительно к итерации по вектору лучше, наверное, использовать int, как мы всегда и делали.
size_t — по стандарту.
Во-первых, очень жаль, что в стандарте используется именно беззнаковый тип. Если бы он был знаковый, то изначально приведённый мною код был бы корректен. А так, несмотря на всю свою внешнюю адекватность, он подвешивает программу.
Ну а во-вторых, если заменить
std::vector
на какой-нибудь другой вектор (например, самописный илиQVector
из библиотеки Qt), то использованиеdecltype
гораздо предпочтительнее, чем запоминание типа для каждого варианта вектора :)Во-первых, size_t это implementation defined. Во-вторых, и у std::vector, и у QVector есть size_type.
PS: А для пробегания в обратном порядке можно воспользоваться boost::adaptors::reverse
Я никоим образом не пытаюсь сказать, что Вы неправы. Всё, о чём вы говорите, безусловно, верно.
Однако концептуальный вес всего того, о чём Вы упомянули (зависимость
size_t
от реализации, наличиеvector::size_type
иboost::adaptors::reverse
), явно больше, чем простое использованиеdecltype
. Ведь достаточно одного беглого взгляда на цикл с его использованием, чтобы понять, что он перебирает элементы вектора. В то же время для понимания конструкциитребуется немало дополнительных знаний.
С другой стороны, стоит отметить, что сама по себе эта конструкция неплоха, так как она надёжна и менее подвержена ошибкам по сравнению с обычным циклом (об этом говорит Страуструп в своём докладе: "используйте стандартные алгоритмы вместо аналогичного самописного кода")
for(auto i=v.size();i--;)
В питоне вообще нет никаких объявлений и, тем не менее, это едва ли не самый читабельный язык на свете.
Автор как раз привел ссылку на мой пост, в котором я ругаюсь на нечитабельность питона..
Автор нашел куда засунуть ссылку... Я пять лет работаю на питоне и мне кажется что ваши проблемы высосаны из пальца. Из всех языков что я знаю, а знаю я их предостаточно, ничего лучше питона по читабельности, на мой взгляд, нет.
У меня, конечно, нет пятилетнего опыта работы с питоном. А пост мой был вызван тем, что столкнувшись с необходимостью правки чужого питоновского кода, я обнаружил, что читать его мне гораздо сложнее, чем я ожидал.
Понятно, что причин этому может быть несколько:
моя собственная убогость;
отсутствие у меня опыта работы с питоном, и привычка к С-образным языкам;
качество кода, с которым мне пришлось столкнуться;
собственно сам питон.
Полностью абстрагироваться от первых двух причины довольно трудно, но я постарался указать на те моменты, которые (по моим ощущениям) мешали читать код больше всего. Обобщенно говоря, это — необходимость дополнительных исследований кода для получения представления о его смысле.
В этом плане, повсеместное использование auto, вероятно, так же плохо.
У меня нет опыта работы с Python'ом, я почти не изучал этот язык, поэтому не могу оценивать его. Однако код, который мне доводилось видеть, был очень похож на магические письмена.
Я думаю, было бы очень хорошо, если бы Вы привели пару примеров, демонстрирующих наглядность и читабельность Python'а.
Чтобы спокойно читать питон надо, естественно, его знать и понимать. Он сильно отличается идеалогически от какого-нибудь С++. Например, функциональный стиль с непривычки многих вышибает в осадок, хотя ничего в нем страшного нет. Или тот же duck typing, надо к нему привыкнуть и не впадать в панику постоянно пытаяся выяснить что же за конкретный тип у переменной. А так, если не знать, то конечно можно долго рассуждать что китайский язык плохой потому что в нем черточки вместо букв.
Читабельность — во-первых, понятие очень субъективное, во-вторых зависит больше от того, кто пишет, чем от языка, на котором он пишет. Я вон даже на perl-е встречал проекты с вполне читаемым кодом. А вот, что делает следующий код на втором питоне, с первого взгляда сказать не могу:
Но догадаться можно, а вот тут даже логика не помогает понять "едва ли не самый читабельный язык на свете":
Ну, естественно, сотворить кровавое месиво можно на любом языке. Хотя, даже тут приведенный вами код и близко не лежал с лучшими образцами на других языках. В последнем блоке пробелы-отступы расставить и все понятно будет.
А так я и говорю что мне довелось покопаться в чужом коде на разных языках. Питон мне (субъективно) понравился больше всех, хотя С# тоже неплох.
По хорошему среда должна подсказать тип i при наведении на нее мышкой.
Я согласен с тем, что использовать auto либо когда код сокращается очень сильно, либо когда его избежать нельзя.
Не могу не отметить еще, что фраза "я не согласен с Саттером" в вопросах, связанных с С++, звучит забавно :о)
Абсолютно согласен. Но, к сожалению, среды, в которых мне приходится работать (Eclipse и QtCreator), пока не умеют делать такие подсказки.
Eclipse не умеет? rly?
Проверил — оказывается, умеет. :)
Спасибо за подсказку.