вторник, 20 июля 2010 г.

Анонс книг 07'2010

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

Прежде чем выбрать книгу я точно так же пытаюсь выяснить ответ на этот вопрос с помощью некоторых критериев, которыми являются «перекрестные ссылки» авторитетных авторов друг на друга, имя самого автора (мало кто сомневается в качестве очередной книги Джеффри Рихтера, его книги можно брать практически не раздумывая), а также рецензии, желательно, так же известных людей.

За последнее время вышло (или собирается выйти) достаточное количество интересных книг, как на русском, так и на английском языках, поэтому я решил сделать подборку книг, которые я либо совсем недавно приобрел, либо с удовольствием бы сделал это в самое ближайшее время. С некоторыми из представленных ниже книг я в той или иной степени знаком лично и мое мнение является более обоснованным, в некоторых других случаях я основываюсь на приведенных выше критериях, поэтому мое мнение вполне может быть не слишком точным (хотя мое мнение в любом случае может не совпасть с вашим). Представленный ниже список – это такой себе wish list из новинок компьютерной литературы, который может стать отправной точкой при выборе книги другими специалистами.

Р. Мартин. Чистый код: создание, анализ и рефакторинг. Питер. 2010

(Оригинал: Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, Prentice Hall, 2008)

clip_image001[4]

Аннотация

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

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

Книга состоит из трех частей. В первой части излагаются принципы, паттерны и приемы написания чистого кода; приводится большой объем примеров кода. Вторая часть состоит из практических сценариев нарастающей сложности. Каждый сценарий представляет собой упражнение по чистке кода или преобразованию проблемного кода в код с меньшим количеством проблем. Третья часть книги — концентрированное выражение ее сути. Она состоит из одной главы с перечнем эвристических правил и «запахов кода», собранных во время анализа. Эта часть представляет собой базу знаний, описывающую наш путь мышления в процессе чтения, написания и чистки кода.

Мнение

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

Во-первых, «дядюшка» Боб Мартин весьма известный человек, автор многих известных и популярных книг и статей, в частности это именно он является автором такого понятия как принципы проектирования S.O.L.I.D. Во-вторых, эта книга очень и очень хорошо принята читательской аудиторией: рейтинг 4,5 на amazon.com (при наличии 67 отзывов), огромное количество положительных отзывов на сайте stackoverflow (искать так), да и в гугле найти отзывов на эту книгу не составит никакого труда (искать так). В-третьих, это высокое качество книги и незабываемый авторский стиль изложения. Тема чистого кода не является новой в компьютерной литературе, об этом сказано достаточно много, но «дядюшка» Боб делает это здорово: в книге рассматривается большое количество примеров кода; стиль изложения отличается ясностью и простотой, да и с чувством юмора у авторов все в порядке.

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

clip_image003[4]

И в этом я пока не разочарован.

P.S. У книги вполне неплохой русский перевод. Встречаются термины, с переводом которых я не согласен, но в целом, русское издание произвело на меня весьма хорошее впечатление (за исключением перевода второго названия книги, которое в оригинале звучит как «A Handbook of Agile Software Craftsmanship», а в русском варианте получилось «Создание, анализ и рефакторинг»).

Дополнительные ссылки

Купить: books.ru, ozon.ru, amazon.com

Обзоры: гугл, stackoverflow

Цитаты: цитатник

К. Дейт. SQL и реляционная теория. Как грамотно писать код на SQL. Символ-Плюс. 2010

(Оригинал: C.J. Date, SQL and Relational Theory, O'Reilly Media, 2009)

clip_image004[4]

Аннотация

Язык SQL распространен повсеместно. Но работать с ним непросто: он сложен, запутан, при написании SQL-команд легко допустить ошибку. Понимание теории, лежащей в основе SQL, - лучший способ гарантировать, что ваш код будет написан правильно, а сама база данных надежна и легко сопровождаема.

В предлагаемой книге К.Дж.Дейт - признанный эксперт, начавший заниматься этими вопросами еще в 1970 году - демонстрирует, как применить реляционную теорию к повседневной практике работы с SQL. Автор подробно объясняет различные аспекты этой модели, рассуждает и доказывает, приводит многочисленные примеры использования этого языка в соответствии с реляционной теорией.

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

Мнение

Крис Дейт является одним из самых авторитетных людей в области реляционных баз данных, на его счету более десяти книг (включая «Введение в системы баз данных»), а также более сотни научных трудов (многие из которых доступны на русском языке на ресурсе citforum.ru). Это последняя книга Криса Дейта, которая на данный момент вышла в свет и она является логическим продолжением одной из его предыдущих книг “Database in Depth: Relational Theory for Practioners”. Все книги Дейта нельзя назвать простыми, их достаточно тяжело читать; в них глубоко рассматриваются теоретические аспекты реляционной модели; в них часто критикуются современные реализации СУБД, основанные на SQL, а многие примеры даются на языке Tutorial D. Если вам не нужен справочник по языку SQL и вы не готовы уделить этой книге достаточное количество времени на изучение этой книги, то, скорее всего, эта книга вам подойдет. Если вам нужен справочник языка SQL или вводный курс по этой теме, то лучше обратить свое внимание на другие источники. Но если вы готовы потратить достаточное количество времени на изучение более фундаментальных вещей, то эта книга может быть вам полезной.

Дополнительные ссылки

Купить: books.ru; ozon.ru; amazon.com

Обзоры: Review by xaprb

Нил Форд и др. 97 этюдов для архитекторов программных систем. Символ-Плюс. 2010

(Оригинал: 97 Things Every Software Architect Should Know, O'Reilly Media, 2009)

clip_image005[4]

Аннотация

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

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

Мнение

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

К этой книге я не могу применить такие эпитеты, как «великолепная», «незабываемая», «восхитительная» и что-то в таком же, поскольку откровенно говоря, правдой это не является. Эту книгу нельзя поставить рядом с книгами Фаулера, Мейера, Буча или Бека, скорее это просто «добротная», «качественная» и «интересная» книга, которая будет полезна большинству профессиональных разработчиков. В ней не раскрываются сакральные знания тайного общества архитекторов, после прочтения которых вы сможете открывать ногами двери к вашему руководителю. Но при этом в книге поднимаются интересные темы, начиная от простых технических советов вроде пользы непрерывной интеграции, заканчивая более философскими вопросами, такими как, совет не быть слишком умным или рекомендации по борьбе с неотъемлемой (essential) сложностью.

Книги с подобным форматом изложения популярны уже несколько десятилетий (вспомните, что знаменитый «Мифический Человеко-месяц» Брукса – это тоже сборник эссе), но в отличие от многих других изданий, в этой книге этюдов получились уж слишком короткими (97 этюдов на 255 страниц). В результате чего, у авторов просто нет возможности раскрыть тему, они могут лишь констатировать некоторые факты, которым читатель может верить, а может и нет. Многие из этих фактов будут полезными даже если читатель никогда о них не слышал, но многие из них за счет своего небольшого объема могут просто проскользнуть мимо сознания читателя и не застрять в голове. Кроме того, большинство из этих этюдов знакомы практически каждому профессиональному разработчику, но за счет своего многообразия практически каждый профессионал найдет что-то интересное для себя.

Дополнительные ссылки

Купить: books.ru; ozon.ru; amazon.com

Обзоры: 1, 2, 3

Электронный вариант: книга доступно, как original, unedited contributions for the book на отдельной wiki-странице издательства OReilly по адресу http://97things.oreilly.com/wiki/index.php/97_Things_Every_Software_Architect_Should_Know_-_The_Book

Jon Skeet. C# in Depth, Second Edition, 28 August 2010

clip_image006[4]

Аннотация

C# has changed significantly since it was first introduced. With the many upgraded features, C# is more expressive than ever. However, an in depth understanding is required to get the most out of the language.

C# in Depth, Second Edition concentrates on the high-value features that make C# such a powerful and flexible development tool. Rather than re-hashing the core of C# that's essentially unchanged since it hit the scene, this book brings readers up to speed with the features and practices that have changed with C# from version 2.0 onwards.

This completely revamped Second Edition is extremely current, covering the new features of C# 4 as well as Code Contracts. Readers will master the subtleties of C#, learning how to tame the trickier bits and apply them to best advantage. Insider tips teach readers how to avoid hidden pitfalls. This book is designed for readers who have learned the basics of C#.

Мнение

В этом году буквально через месяц (28 августа 2010 года) должно выйти продолжение замечательной книги Джона Скита (Jon Skeet) C# In Depth. Если говорить о предыдущем издании, то можно однозначно сказать, что это одна из самых сильных книг о языке C#, которая когда-либо выходила в свет (да, и не путайте с книгами Рихтера; Рихтер все же пишет о платформе .net, а Скит – о языке C#). Книга действительно отвечает своему названию; темы рассматриваются очень глубоко, но несмотря на это стиль изложения простой и понятный, с большим количеством хороших наглядных примеров.

Кроме того, даже если вы не интересуетесь компьютерной литературой, вы могли встречать имя Джона Скита и ранее. Он автор весьма популярного блога и №1 на сайте stackoverflow, так что вполне возможно, что когда вы искали ответ на какой-то вопрос, связанный с языком C# или платформой .net, вы сталкивались с ответами или статьями этого человека.

Так что я не знаю, как вы, но я жду не дождусь выхода этой книги в свет.

Дополнительные ссылки

Купить: amazon.com

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

Anders Hejlsberg et al. The C# Programming Language. 4th Edition. 18 October, 2010

clip_image007[4]

Аннотация

“Based on my own experience, I can safely say that every .NET developer who reads this will have at least one ‘aha’ moment and will be a better developer for it.”

–From the Foreword by Don Box

The popular C# programming language combines the high productivity of rapid application development languages with the raw power of C and C++. The C# Programming Language, Fourth Edition, is the authoritative and annotated technical reference for C# 4.0.

Written by Anders Hejlsberg, the language’s architect, and his colleagues, Mads Torgersen, Scott Wiltamuth, and Peter Golde, this volume has been completely updated and reorganized for C# 4.0. The book provides the complete specification of the language, along with descriptions, reference materials, code samples, and annotations from nine prominent C# gurus.

The many annotations bring a depth and breadth of understanding rarely found in any programming book. As the main text of the book introduces the concepts of the C# language, cogent annotations explain why they are important, how they are used, how they relate to other languages, and even how they evolved.

This book is the definitive, must-have reference for any developer who wants to understand C#.

Мнение

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

Можно подумать (да я и сам так думал длительное время), что чтение спецификации – это не слишком разумное занятие, которое не способно принести ощутимой пользы простому смертному, но это не так. Хотя спецификация написана сухим языком, в ней содержится масса полезной информации, которая может с легкостью заменить вам как MSDN, так и книги других авторов. Такую книгу нельзя (хотя, скорее тяжело) читать от корки до корки; но вполне можно возвращаться к ней снова и снова, когда возникают какие-то спорные ситуации или вам нужно выяснить, какое поведение должно быть в каком-то конкретном случае. Кроме того, аннотации, написанные признанными экспертами в своей области, дополняют столь формальное изложение и делают чтение этой книги более приятным занятием.

Дополнительные ссылки

Купить: amazon.com

Обзоры предыдущего издания: 1, 2

B. Meyer. Touch of Class: Learning to Program Well with Objects and Contracts. 2009

clip_image009[4]

Аннотация

From object technology pioneer and ETH Zurich professor Bertrand Meyer, winner of the Jolt award and the ACM Software System Award, a revolutionary textbook that makes learning programming fun and rewarding. Meyer builds his presentation on a rich object-oriented software system supporting graphics and multimedia, which students can use to produce impressive applications from day one, then understand inside out as they learn new programming techniques.

Unique to Touch of Class is a combination of a practical, hands-on approach to programming with the introduction of sound theoretical support focused on helping students learn the construction of high quality software. The use of full color brings exciting programming concepts to life.

Among the useful features of the book is the use of Design by Contract, critical to software quality and providing a gentle introduction to formal methods.

Will give students a major advantage by teaching professional-level techniques in a literate, relaxed and humorous way.

Мнение

Эту книгу сегодня очень тяжело назвать новинкой, ведь с момента ее выхода прошел уже почти год, но мне бы, все же, хотелось акцентировать на ней внимание, поскольку она получила незаслуженно мало внимания со стороны компьютерного сообщества. Это учебник, написанный одним из наиболее серьезных ученых в области объектно-ориентированного программирования, Бертраном Мейером, который основывается на курсе Computer Science Цюрихского университете. Мне очень жаль, что за эту книгу не взялось никакое из российских издательств, поскольку это был бы отличный учебник для студентов и многих других специалистов, которые хотят пополнить свои знания в различных областях компьютерных наук. Эта книга покрывает широкий спектр тем, начиная от понятия переменных, базовых структур данных и алгоритмов, заканчивая лямбда-вычислениями, объектно-ориентированным программированием и проектированием по контракту. Так что я очень надеюсь, что наши издатели все же изменят свое мнение и, все-таки обратят внимание на очередную книгу профессора Мейера.

Дополнительные ссылки

Купить: amazon.com

Дополнительные ссылки: официальный сайт книги; Бертран Мейер о выходе новой книги

среда, 14 июля 2010 г.

Строгие перечисления на C++

Эта статья опубликована в журнале RSDN Magazine 4, 2009 в соавторстве с Дмитрием Вьюковым (a.k.a. remark).

Проблема

Перечисления (enumerations) в языках программирования С и С++ имеют ряд особенностей, которые могут оказать влияние (иногда весьма неприятное) на работу как индивидуального разработчика, так и команды целиком.

Основные проблемы перечислений в этих языках следующие:

Во-первых, имена своих констант перечисление вносит в окружающее пространство имен, поэтому внутри одного и того же класса, структуры или пространства имен нельзя определить два перечисления с одинаковыми значениями литеральных констант. Но даже если конфликтов имен не происходит, то перечисление все равно «загрязняет» внешнее пространство имен, что негативно сказывается на работе с кодом, в частности на работе таких инструментов как IntelliSence.

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

В-третьих, отсутствует возможность указать нижележащий тип для перечисления. В настоящий момент размер (sizeof) перечисления определяется следующим образом [Страуструп2004]:

«Размер (sizeof) перечисления является размером некоторого интегрального типа, который в состоянии содержать весь диапазон значений перечисления. Результат не больше, чем sizeof(int) при условии, что элементы перечисления представимы в виде int или unsigned int. Например, sizeof(e1) может равняться 1 или 4, но не 8 на машине, где sizeof(int) == 4

ПРИМЕЧАНИЕ
На самом деле, sizeof(e1) может равняться 1, 2 или 4 (а не только 1 или 4, как об этом пишет Страуструп) но не 8

В-четвертых, отсутствует расширяемость. Т.е. если понадобиться "связать" значение перечисления с текстовым описанием или любыми другими данными произвольного типа, придется писать свободные функции, использование которых не всегда является удобным. Хотя эта проблема может показаться незначительной, иногда она может приводить к ошибкам и усложнять сопровождение кода. Хотя ничего сложного в реализации подобных функции нет, вызов ее довольно прост, а поиск имен (Argument-dependent lookup) зачастую упрощает вызов, необходимость изменения функции при добавлении значения перечисления может приводить к ошибкам. А если требуется связать значение перечисления с несколькими типами объектов и эти связи являются важными с точки зрения бизнес-логики приложения, то проблемы с расширяемостью вполне стоят того, чтобы найти для них более подходящее решение.

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

Подобного рода проблемы давно известны и часто обсуждаются компьютерным сообществом в различных блогах, форумах или конференциях. Первым, кто обратил внимание на проблему с перечислениями, был ни кто иной, как Бьярн Страуструп в своей книге «Дизайн и эволюция С++» [Страуструп2006]:

«В С концепция перечислений выглядит незаконченной. В первоначальном варианте языка их не было. Перечисления ввели без всякой охоты в качестве уступки тем, кто настойчиво требовал более основательных символических констант, чем препроцессорные макросы без параметров. Поэтому в С значение перечислителя имеет тип int, равно как и значение переменной, объявленной как имеющая тип перечисления. Значение типа int можно присваивать переменной типа перечисления. … Для тех стилей программирования, которые должен был поддерживать С++, не было нужды в перечислениях, поэтому правила С перешли в С++ без изменения.»

Помимо Страуструпа, на эту проблему обратил внимание Герб Саттер в своей статье “Enumerations” [Саттер2004]. И хотя пример Герба немного утрирован, он хорошо демонстрирует основные проблемы перечислений, а также показывает основное направление решения. Также благодаря Гербу Саттеру, возможно, в следующей редакции стандарта языка С++ мы увидим статически-типизированные перечисления, которые решат большую часть поднятых здесь вопросов (подробности можно почитать в [Саттер2007]). Но до тех пор нам не остается ничего другого, кроме как либо игнорировать эту проблему, либо попытаться собственными силами найти приемлемое решение.

Возможные решения

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

Решение 1. Соглашения по именованию

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

enum Color
{
   CLR_Red,
   CLR_Green,
   CLR_Black,
   //...
};

либо

enum Color
{
   Color_Red,
   Color_Green,
   Color_Black,
   //...
};

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

Решение 2. Вариант Герба Саттера

Второй вариант решения предложил Герб Саттер в своей статье [Саттер2004]. Его вариант решает проблему с именами и убирает возможность неявного преобразования значения перечисления к интегральному типу.

class Color {
  enum Color_ { Red_, Green_, Blue_, Purple_, Violet_ };
  Color_ value;
public:
  static const Color Red, Green, Blue, Purple, Violet;
  explicit Color( Color& other )    
    : value( other.value ) { }
  bool operator<( Color const& other )   
    { return value < other.value; }
  bool operator==( Color const& other )    
    { return value == other.value; }
    // etc.
  int ToInt() const     { return value; }
};
const Color Color::Red( Color::Red_ );
const Color Color::Green( Color::Green_ );
const Color Color::Blue( Color::Blue_ );
const Color Color::Purple( Color::Purple_ );
const Color Color::Violet( Color::Violet_ );
Решение 3. На основе структуры

Одно из самых простых решений проблемы внесения значений перечислений в окружающее пространство имен, предложил Дмитрий Вьюков (aka remark) на rsdn.ru [Вьюков2007_1]. Исходный вариант решения устраняет проблему вынесения констант перечисления в окружающее пространство имен, а также частично решает проблему типизации (т.к. не позволяет неявное преобразование интегральных констант к перечислимому типу, но в то же время позволяет сравнивать типы различных перечислений). Немного исправленный вариант исходного решения позволит повысит статическую типизацию перечислений, при этом оставаясь достаточно простым в реализации и сопровождении..

ПРИМЕЧАНИЕ
Существует альтернативное решение, когда вместо структуры используется пространство имен (namespace)

struct Color
{
    enum Type
    {
        Red, Green, Black
    };
    Type t_;
    Color(Type t) : t_(t) {}
    operator Type () const {return t_;}
private:
  // Предотвращает неявное преобразование значений перечисления
// к любым типам, кроме типа type, что препятствует сравнению
// значений перечислений с интегральными типами или со значениями
// других перечислений
template<typename T>
operator T () const;
};

Пример использования:

Color c = Color::Red;
switch(c)
{
   case Color::Red:
     //некоторый код
   break;
}
Color2 c2 = Color2::Green;
c2 = c; // Ошибка компиляции!
c2 = 3; // Ошибка компиляции!
if (c2 == Color::Red ) {} // Ошибка компиляции!
if (c2) {} //Ошибка компиляции!

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

#define DEFINE_SIMPLE_ENUM(EnumName, seq) \
struct EnumName {\
   enum type \
   { \
      BOOST_PP_SEQ_FOR_EACH_I(DEFINE_SIMPLE_ENUM_VAL, EnumName, seq)\
   }; \
   type v; \
   EnumName(type v) : v(v) {} \
   operator type() const {return v;} \
private:
template<typename T>
operator T () const;};\

#define DEFINE_SIMPLE_ENUM_VAL(r, data, i, record) \
BOOST_PP_TUPLE_ELEM(2, 0, record) = BOOST_PP_TUPLE_ELEM(2, 1, record),

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

DEFINE_SIMPLE_ENUM(Color,
  ((Red, 1))
  ((Green, 3))
  )

Способ использование созданного перечисления при этом никак не изменится.

Вариант 4. Строгое перечисление

Четвертый вариант – это немного модифицированный вариант решения, предложенный Дмитрием Вьюковым (aka remark) на форуме rsdn.ru [Вьюков2007_2].

Это наиболее функциональный, но и наиболее сложный в реализации вариант, который позволяет следующее:

1.    Задавать тип, лежащий в основе перечисления. По-умолчанию, как и у встроенных перечислений применяется тип int, но в случае необходимости пользователь может указать другой интегральный тип при определении перечисления.

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

3.    Задать внешнее имя (ExternalName). Например, для вывода пользователю.

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

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

6.    Получить все значения перечисления (EnumType::GetEntities). По-сути это получение «метаданных» перечислимого типа, аналогичное тому как это делается во многих других языках программирования с помощью рефлексии (reflection). С помощью этого можно решать самые различные задачи, начиная от привязки перечисления к выпадающим спискам (combo box) пользовательского интерфейса, заканчивая явным перебором всех возможных значений перечисления для каких-либо бизнес-целей.

7.    Связать значения перечисления с объектами произвольных классов или примитивными типами. Хотя эта функциональность может показаться излишней, возможность связывания значения перечисления с произвольным значением другого типа может быть весьма полезной. Таким способом можно связать значение одного перечисления с другим, получив категорию перечислений, а связав значение перечисления с некоторым типом можно удобным способом реализовать шаблон проектирования «Метод шаблона». Это тоже является добавлением своего рода метаинформации и может помочь в решении множества других задач.

Начнем с простого варианта использования.

Определение перечисления.

DEFINE_STRICT_ENUM(Color, int, 
((Red,      1))
((Black,    3))
((Green,    5))
)

Пример использования

Color c = Color::Red;
std::cout<<"Value: "<<c.GetValue()
        <<", Internal name: "<<c.InternalName()<<std::endl;

Color::Entries entries = Color::GetEntries();
for(size_t i = 0; i < entries.size(); ++i)
{
    std::cout<<entries[i].Value()<< "\t"
        <<entries[i].InternalName()
        <<"\t"<<entries[i].ExternalName()<<std::endl;
}
switch(c.GetValue())
{
    //Использовать выражение Color::Red().GetValue() в блоках
    //case нельзя, т.к. это выражение не является константой
    //времени компиляции.
    //Специально для этой цели внутри каждого класса
    //определяется внутреннее перечисление, которое содержит
    //все значения констант с завершающим символом “_”
    case Color::Red_:
      std::cout<<”Hello, Red color!”<<std::endl;
      break;

Результат выполнения:

Value: 1 Internal Name: Red
1    Red
3    Black
5    Green

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

DEFINE_STRICT_ENUM_WITH_NAME( Color, int, true,
((Red,   1, "Red"))
((Black, 3, "Black"))
((Green, 5, "Green"))
)

DEFINE_STRICT_ENUM_WITH_DESC( Color, int, false,
((Red,   1, "Red color",   "Красный"))
((Black, 3, "Black color", "Черный"))
((Green, 5, "Green color", "Зеленый"))
)
Color c = Color::Red;
std::cout<<"Value: "<<c.GetValue()
        <<", Internal name: "<<c.InternalName()
        <<", External name: "<<c.ExternalName()<<std::endl;

Color::Entries entries = Color::GetEntries();
for(size_t i = 0; i < entries.size(); ++i)
{
    std::cout<<entries[i].Value()<< "\t"
        <<entries[i].InternalName()
        <<"\t"<<entries[i].ExternalName()<<std::endl;
}

Результат выполнения:

Value: 1, Internal Name: Red,
1    Red color      Красный
3    Black color    Черный
5    Green color    Зеленый

Третий вариант использования показывает привязку значений перечислений с объектами произвольных классов (в данном случае производится привязка одного перечисления со значениями другого перечисления):

DEFINE_STRICT_ENUM(ColorCategory, int,
                   ((Bright, 1))
                   ((Dark, 2))
                   )

DEFINE_STRICT_ENUM_TAG(Color, int, enum_strict, ColorCategory,
                   ((Red,      1, (ColorCategory::Bright) ))
                   ((Black,    3, (ColorCategory::Dark)   ))
                   ((Green,    5, (ColorCategory::Bright) )) )

Пример использования.

Color c = Color::Red;
std::cout<<"Value: "<<c.GetValue()
        <<", Internal name: "<<c.InternalName()
        <<", Category: "<<c.Tag().InternalName()<<std::endl;

Результат выполнения

Value: 1, Internal Name: Red, Category: Bright

ПРИМЕЧАНИЕ
Полный исходный код реализации приведен в конце статьи

Несмотря на богатую функциональность, у этого решения также имеются недостатки:

1.    Каждое значение перечисления обязательно нужно задавать явно. Нельзя сказать, что это ограничение присуще каждому сценарию использования. Бывают случаи, когда без явного задания перечисления все равно не обойтись (например, при реализации коммуникационных протоколов, либо при строгом соответствии значений перечисления каким-либо известным константам).

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

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

4.    Усложняется просмотр значений переменных перечисления в отладчике

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

ПРИМЕЧАНИЕ
Приведенная реализация строгих перечислений имеет ряд ограничений. Во-первых, реализация не предусматривает использование перечислений из потоков, выполнение которых начинается до выполнения функции main. Во-вторых, реализация не является безопасной с точки зрения многопоточности: если первое обращение к перечислению произойдет из нескольких потоков одновременно, это приведет к созданию двух экземпляров «метаданных» перечисления. В-третьих, реализация не является переносимой (работает только на VC++2005/2008 и, скорее всего VC++2010), поскольку используются нестандартные расширения языка, а именно __declspec(selectany). 

Визуализация строгих перечислений

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

По-умолчанию, переменные перечислений в отладчике выглядят следующим образом.
Figure1

Рисунок 1 – Отображение строгого перечисления по-умолчанию

Для тех, кого такой вариант отображения не устраивает, разработчики отладчика Visual Studio предусмотрели вариант изменения способа отображения объектов произвольных классов с помощью файла autoexp.dat, который расположен в папке Microsoft Visual Studio 8\Common7\Packages\Debugger\.

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

StrictEnumBase<*>{
  preview(
    #(<str>"Name "</str>, $e.valueName_, <str>", Value "</str>, $e.value_)
  )
}

Color{
  preview(
    #(<str>"Name "</str>, $e.valueName_, <str>", Value "</str>, $e.value_)
  )
}

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

Figure2

Рисунок 2 – Визуализация строгого перечисления в отладчике

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

ПРИМЕЧАНИЕ
В текущем разделе показаны примеры решения проблемы визуализации для Microsoft Visual C++ 2005 и выше, но аналогичные решения можно найти и для других сред разработки, в частности GDB также поддерживает аналогичную функциональность.

Вместо заключения

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

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

Литература

1.    Бьярн Страуструп. Язык программирования С++. Бином. 2004
2.    Бьярн Страуструп. Дизайн и эволюция С++. Питер. 2007
3.    Herb Sutter, Jum Hyslop, C/C++ Users Journal, 22(5), May 2004
4.    Herb Sutter, David E. Miller, Bjarne Stroustrup Strongly Typed Enums (revision 3), July 2007
5.    Дмитрий Вьюков. [Trick] Делаем правильные enum'ы. http://rsdn.ru/forum/cpp/2647727.aspx. Сентябрь 2007
6.    Дмитрий Вьюков. Enum с тэгом. http://rsdn.ru/forum/cpp/2655200.aspx. Сентябрь 2007

Исходный код строгого перечисления
#pragma once
#ifndef __STRICT_ENUM__
#define __STRICT_ENUM__

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <boost\preprocessor.hpp>

namespace detail
{
    /**   Пустой тэг для связывания со значением enum'а */
    struct EmptyTag
    {
    };
}

/**   Для задания признака то, является перечисление строгим
*   (при этом генерируется исключение при попытке установить
*    значение перечислению отличное от заданного) или нет*/
enum Strictness
{
enum_strict,    //!< перечисление является строгим
enum_nonstrict, //!< перечисление не является строгим (по-умолчанию)
};

/**   Базовый класс для типобезопасных перечислений
*
*    Пример использования смотри в конце файла
*/
template<typename Derived, typename Type = int,
Strictness strict = enum_nonstrict,
typename TagType = detail::EmptyTag>
class StrictEnumBase
{
    /**   Класс с описанием значения перечисления */
    class EntryDescription
    {
    public:
        /**   Конструктор */
        EntryDescription(Type value, const std::string& internalName,
const std::string& externalName)
            : value_(value)
            , internalName_(internalName)
            , externalName_(externalName)
        {}

/**   Значение */
        Type Value() const
        {
            return value_;
        }

/**   Внутреннее имя */
        std::string InternalName() const
        {
            return internalName_;
        }

/**   Внешнее имя */
        std::string ExternalName() const
        {
            return externalName_;
        }

    private:
/*const*/ Type value_; //!< значение
        /*const*/ std::string internalName_; //!< внутреннее имя
        /*const*/ std::string externalName_; //!< внешнее имя
    };
   
/**   Информация, сопоставляемая с каждым элементом -
      описание и пользовательский тэг */
    class EntryInfo
    {
    public:
        /**   Конструктор
         */
        EntryInfo(const EntryDescription& desc, const TagType& tag)
            : desc_(desc)
            , tag_(tag)
        {}
       
        /**   Получить описание
         */
        const EntryDescription& Description() const
        {
            return desc_;
        }

        /**   Получить тэг
         */
        const TagType& Tag() const
        {
            return tag_;
        }

    private:
        /*const*/ EntryDescription desc_;  //!< описание
        /*const*/ TagType tag_;            //!< тэг
    };


    /**   Внутренний тип для хранения информации об элементах */
    typedef std::map<Type, EntryInfo> IntEntries;

public: //Comparison operators
    bool operator < (const StrictEnumBase& other) const
    {
        return value_ < other.value_;
    }

    bool operator > (const StrictEnumBase& other) const
    {
        return value_ > other.value_;
    }

    bool operator <= (const StrictEnumBase& other) const
    {
        return value_ <= other.value_;
    }

    bool operator >= (const StrictEnumBase& other) const
    {
        return value_ >= other.value_;
    }

    bool operator == (const StrictEnumBase& other) const
    {
        return value_ == other.value_;
    }

    bool operator != (const StrictEnumBase& other) const
    {
        return value_ != other.value_;
    }
public: //Public interface
/**   Получить значение перечисления */
    const Type& Value() const
    {
        return value_;
    }

/**   Получить описание значения.
*    Можно получать и для неизвестных значений
*/
    EntryDescription Description() const
    {
        IntEntries::iterator iter = GetIntEntries().find(value_);
        if (iter == GetIntEntries().end())
            return MakeEmptyDescription(value_);
        return iter->second.Description();
    }

/**   Получить тэг, связанный со значением
    *    Можно получать только для известных значений
    *    @return Тэг
    *    @throw std::out_of_range Если вызывается для
*        неизвестного значения
    */
    const TagType& Tag() const // throw (std::out_of_range)
    {
        IntEntries::iterator iter = GetIntEntries().find(value_);
        if (iter == GetIntEntries().end())
            throw std::out_of_range("StrictEnum: out of range");
        return iter->second.Tag();
    }


/**   Внутреннее имя значения перечисления */
std::string InternalName() const
{
return Description().InternalName();
}

/**   Внешнее имя значения перечисления */
std::string ExternalName() const
{
return Description().ExternalName();
}

/**   Проверить, является ли значение известным */
    bool IsKnown() const
    {
        return IsExist(value_);
    }
public: //Static functions
/**   Тип для хранения информации об элементах */
typedef std::vector<EntryDescription> Entries;

/**   Проверка существования заданного значения в перечислении */
    static bool IsExist(Type value)
    {
return GetIntEntries().find(value) != GetIntEntries().end();
    }

/**  Создать объект из значения
*   @throw std::out_of_range Если устанавливается неправильное
*   значение (только в случае strict == enum_strict).
*/
    static Derived FromValue(Type value)
    {
AssertExist(value);
        return Derived(value);
    }

/**   Создать объект из значения. Если значение неправильное,
*    то возвращает значение по-умолчанию
*    @param defaultValue Значение по-умолчанию
*/
    static Derived FromValue(Type value, Derived defaultValue)
    {
        if (IsExist(value))
            return Derived(value);
        else
            return defaultValue;
    }

/**   Получить описания всех известных значений перечисления */
static Entries GetEntries()
    {
        // Копируем описания элементов из внутреннего
// представления во внешнее
        IntEntries intEntries = GetIntEntries();
        Entries entries;
        entries.reserve(intEntries.size());
        for (IntEntries::const_iterator iter = intEntries.begin();
iter != intEntries.end(); ++iter)
            entries.push_back(iter->second.Description());
        return entries;
    }

protected:
/**   Тип значения */
    typedef Type ValueType;

/**   Конструктор для известных значений */
    StrictEnumBase(Type value, const std::string& internalName,
const std::string& externalName, const TagType& tag = TagType())
        : value_(value)
, valueName_(internalName)
    {
        GetIntEntries().insert(std::make_pair(value,
EntryInfo(EntryDescription(value, internalName, externalName),
tag)));
    }

    /**   Конструктор для неизвестных значений */
    explicit StrictEnumBase(Type value)
        : value_(value)
    {
    }

private:
/**   Непосредственно значение */
    Type value_;
/**
*    Строковое представление значения
*  (нужно только для визуализации, в противном случае можно удалить)
*/
std::string valueName_;

    /**   Получить множество значений, которые могут содержаться
*    в данном перечислении
    */
    static IntEntries& GetIntEntries()
    {
        static IntEntries entries;
        return entries;
    }

  /**    Проверить, что значение является известным
     *    @throw std::out_of_range Если неизвестное значение
     */
    static void AssertExist(Type value)
    {
        if (strict == enum_strict && !IsExist(value))
            throw std::out_of_range("StrictEnum: out of range");
    }

    /**   Создать описание для неизвестного значения
    */
    static EntryDescription MakeEmptyDescription(Type value)
    {
        std::ostringstream stream;
        stream << "<" << value << ">";
        return EntryDescription(value, stream.str(), "");
    }
};

#define DEFINE_STRICT_ENUM(EnumName, Type, seq) \
class EnumName : public tools::common::StrictEnumBase<EnumName, Type> { \
friend class tools::common::StrictEnumBase<EnumName, Type>; \
EnumName(ValueType value, const std::string& sIntName, const std::string& sExtName) \
: tools::common::StrictEnumBase<EnumName, Type>(value, sIntName, sExtName) \
{} \
EnumName(ValueType value) \
: tools::common::StrictEnumBase<EnumName, Type>(value) \
{} \
public: \
enum { \
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_VAL, EnumName, seq) \
    }; \
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_DEF, EnumName, seq);\
};\
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_DECL, EnumName, seq);

#define DEFINE_STRICT_ENUM_DEF(r, aux, i, record) \
static const aux BOOST_PP_TUPLE_ELEM(2, 0, record);

#define DEFINE_STRICT_ENUM_VAL(r, aux, i, record) \
BOOST_PP_TUPLE_ELEM(2, 0, record)_ = BOOST_PP_TUPLE_ELEM(2, 1, record),


#define DEFINE_STRICT_ENUM_DECL(r, aux, i, record) \
__declspec(selectany) const aux aux::BOOST_PP_TUPLE_ELEM(2, 0, record)(BOOST_PP_TUPLE_ELEM(2, 1, record), BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(2, 0, record)), "");

#define DEFINE_STRICT_ENUM_TAG(EnumName, Type, strict_f, tag_t, seq) \
class EnumName : public tools::common::StrictEnumBase<EnumName, Type, strict_f, tag_t> { \
friend class tools::common::StrictEnumBase<EnumName, Type, strict_f, tag_t>; \
EnumName(ValueType value, const std::string& sIntName, const std::string& sExtName, tag_t tag) \
: tools::common::StrictEnumBase<EnumName, Type, strict_f, tag_t>(value, sIntName, sExtName, tag) \
{} \
EnumName(ValueType value) \
: tools::common::StrictEnumBase<EnumName, Type, strict_f, tag_t>(value) \
{} \
public: \
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_TAG_DEF, EnumName, seq);\
};\
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_TAG_DECL, EnumName, seq);

#define DEFINE_STRICT_ENUM_TAG_DEF(r, aux, i, record) \
static const aux BOOST_PP_TUPLE_ELEM(3, 0, record);

#define DEFINE_STRICT_ENUM_TAG_DECL(r, aux, i, record) \
__declspec(selectany) const aux aux::BOOST_PP_TUPLE_ELEM(3, 0, record)(BOOST_PP_TUPLE_ELEM(3, 1, record), BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(3, 0, record)), "", BOOST_PP_TUPLE_ELEM(3, 2, record));



#define DEFINE_STRICT_ENUM_WITH_DESC(EnumName, Type, seq) \
class EnumName : public tools::common::StrictEnumBase<EnumName, Type> { \
    friend class tools::common::StrictEnumBase<EnumName, Type>; \
    EnumName(ValueType value, const std::string& internalName, const std::string& externalName) \
    : tools::common::StrictEnumBase<EnumName, Type>(value, internalName, externalName) \
        {} \
    EnumName(ValueType value) \
        : tools::common::StrictEnumBase<EnumName, Type>(value) \
    {} \
public: \
    BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_WITH_DESC_DEF, EnumName, seq);\
};\
    BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_WITH_DESC_DECL, EnumName, seq);

#define DEFINE_STRICT_ENUM_WITH_DESC_DEF(r, aux, i, record) \
    static const aux BOOST_PP_TUPLE_ELEM(4, 0, record);

#define DEFINE_STRICT_ENUM_WITH_DESC_DECL(r, aux, i, record) \
    __declspec(selectany) const aux aux::BOOST_PP_TUPLE_ELEM(4, 0, record)(BOOST_PP_TUPLE_ELEM(4, 1, record), BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(4, 2, record)), BOOST_PP_TUPLE_ELEM(4, 3, record));

#define DEFINE_STRICT_ENUM_WITH_NAME(EnumName, Type, seq) \
class EnumName : public tools::common::StrictEnumBase<EnumName, Type> { \
friend class tools::common::StrictEnumBase<EnumName, Type>; \
EnumName(ValueType value, const std::string& internalName, const std::string& externalName) \
: tools::common::StrictEnumBase<EnumName, Type>(value, internalName, externalName) \
{} \
EnumName(ValueType value) \
: tools::common::StrictEnumBase<EnumName, Type>(value) \
{} \
public: \
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_WITH_NAME_DEF, EnumName, seq);\
};\
BOOST_PP_SEQ_FOR_EACH_I(DEFINE_STRICT_ENUM_WITH_NAME_DECL, EnumName, seq);

#define DEFINE_STRICT_ENUM_WITH_NAME_DEF(r, aux, i, record) \
static const aux BOOST_PP_TUPLE_ELEM(3, 0, record);

#define DEFINE_STRICT_ENUM_WITH_NAME_DECL(r, aux, i, record) \
__declspec(selectany) const aux aux::BOOST_PP_TUPLE_ELEM(3, 0, record) \
(BOOST_PP_TUPLE_ELEM(3, 1, record), BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(3, 0, record)), "");


/*
Примеры использования строгих перечислений:

DEFINE_STRICT_ENUM(Color, int,
((Red,      1))
((Black,    3))
((Green,    5))
)
 DEFINE_STRICT_ENUM_TAG(Color, int, enum_strict, ColorCategory,
((Red,      1, (ColorCategory::Bright) ))
((Black,    3, (ColorCategory::Dark)   ))
((Green,    5, (ColorCategory::Bright) ))
)
*/
#endif //__STRICT_ENUM__