пятница, 8 июля 2011 г.

Перечисления

В последнее время мне полюбился вот такой способ задания всякого рода перечислений:
1 #define SOME_ENUM_PRINT(P) \ 2 P(eNone, 0, "Нет") \ 3 P(eCmd57_Ok, 1, "Команда 57 ОК") \ 4 P(eCmd57_Cancel, 2, "Команда 57 Отмена") \ 5 P(eCmd58, 4, "Команда 58") \ 6 P(eTimer, 8, "Таймер") \ 7 P(ePrintOk, 16, "Печать завершена") \ 8 P(ePrintError, 32, "Ошибка печати") 9 10 enum ESomeEnum { 11 #define P(n, no, s) n = no, 12 SOME_ENUM_PRINT(P) 13 #undef P 14 }; 15 16 // а потом можно вот так: 17 const char * str(ESomeEnum e) 18 { 19 switch(e) 20 { 21 #define P(n, no, s) case n: return s; 22 SOME_ENUM_PRINT(P) 23 #undef P 24 default: return "Unknown"; 25 } 26 }

Понятно, что кроме текста, можно добавить произвольную информацию.  С помощью такого макроса легко строить всякие таблицы, создавать куски кода, работающие с каждым членом перечисления и т.д. Как бонус, если изменить перечисления, то многие связанные с этим вещи обновятся автоматически. Ну и последнее - вся информация в одном месте.
При этом все довольно просто и понятно Улыбка На рсдн была статья, в которой описывалось как создавать “строгие” перечисления и ассоциировать с ними доп инфу. Однако код в ней значительно сложнее Улыбка

среда, 29 июня 2011 г.

Склероз

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

А потом, через полгода-год старая задача снова становится актуальной. Но я уже почти все о ней забыл. Остались какие-то смутные воспоминания и рабочие записи, в которых теперь ничего не понятно. И снова все начинается заново – читаешь доки, наступаешь на те же грабли. Что-то конечно вспоминается, но далеко не все.

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

Видимо надо завести правило – если работа над задачей остановилась больше чем на неделю, то надо потратить день-два и подробно (очень подробно) все записать по горячим следам.

Кстати, рекомендую http://trac.edgewall.org/ – хорошая система учета доки, багов, записей и заодно просмотр коммитов в системе контроля версий. Без проблем ставится под виндоуз, причем в простейшем варианте не требуется веб-сервер.

пятница, 3 июня 2011 г.

Scons tools

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

SCons скрипты часто используют какие-то общие части. Раньше я такие части выносил в отдельные питоновские модули и подключал их через import в билд-скрипт. Однако так как эти модули лежали не рядом со скриптом, и не в PYTHONPATH, возникал ужас вида

 
1 import os
2 import sys
3 from os import path
4
5 sys.path.append(path.normpath(path.join(os.getcwd(), "..", "common", "build")))
6
7 from myModule import SomeFunc

Так вот, тулз – это просто модуль питона, который содержит две функции – generate(env, **kw) и exists(env, **kw).


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


Теперь все становится очень просто:



1 e = Environment(tools=('MyTool', ), toolpath=('../common/build',))
2 e.SomeFunc(...)


Ну и сам тулз выглядит вот так:



1 def generate(env):
2 env.AddMethod(SomeFunc)
3
4 def exists(env):
5 return 1
6
7 def SomeFunc(env):
8 pass


Полезно читать доку Улыбка

вторник, 15 марта 2011 г.

Я люблю c++

1 void f()
2 {
3 int t;
4 SomeType obj(OtherType(t));
5 obj.method();
6 }

Что такое obj? Казалось бы – переменная типа SomeType. Однако на самом деле – это объявление функции SomeType obj(OtherType t);


Причем на строку 5 MSVC даст вот такую ошибку:


error C2228: left of '.method' must have class/struct/union


Уже не первый раз напарываюсь, но в ступор все равно вгоняет на пару минут…


Чтобы работало, надо заключить OtherType в скобки:


SomeType obj( (OtherType) (t));


Получается так потому, что:


1. Разрешены локальные объявления функций – слабо понятно зачем такое вообще может кому-нить понадобиться.


2. int (t) – это вполне корректное определение переменной t типа int – опять же слабо понятно почему разрешен такой синтаксис.


 

понедельник, 14 марта 2011 г.

shared_ptr vs auto_ptr

Был такой код:

struct IInterf
{
virtual void func() = 0;
};
boost::shared_ptr
<IInterf> PInterf;

PInter createObj();

Через некоторое время стало ясно, что у объекта будет конкретный владелец и я заменил shared_ptr на auto_ptr.


И словил access violation при завершении программы.


Минут через 5 сообразил, что деструктор не виртуальный; shared_ptr динамически создает “удалятель”, зная истинный тип IInterf, а auto_ptr просто зовет delete p.


Когда я писал IInterf, то подумал что он будет всегда обернут в shared_ptr и решил не писать виртуальный деструктор.


Мораль сей басни – не экономьте на спичках :)

пятница, 11 марта 2011 г.

boost::exception

Недавно потребовалось как-то упорядочить исключения в программе. Посмотрел в сторону boost::exception, благо boost  в проекте уже используется.

Библиотечка позволяет решить две проблемы:

  1. Добавлять к исключению любого типа (производного от boost::exception) произвольную информацию, причем не только в месте выброса, но и на более высоких промежуточных уровнях обработки
  2. Клонировать текущее исключение, сохранять его и выкидывать в другом месте, а также “вкладывать” в другое исключение.

Пока остановимся на первом пункте, о втором я расскажу в очередном посте.

Сохранение в исключении произвольной информации

Выглядит примерно так:

1 typedef boost::error_info<struct errinfo_str_, std::string> errinfo_str; // 1
2 typedef boost::error_info<struct errinfo_file_, Handle> errinfo_file;
3
4 ...
5 try
6 {
7 try
8 {
9 throw MyException() << errinfo_file(hfile); // 2
10 }
11 catch(MyException &e)
12 {
13 e << errinfo_str("bla-bla"); // 3
14 throw;
15 }
16 }
17 catch(const boost::esception &e)
18 {
19 std::cout << diagnostic_information(e); // 4
20 if(std::string const * inf=get_error_info<errinfo_str>(e) ) //5
21 std::cout << "Info: " << *inf << "\n";
22 }
23
24

Основная идея – мы можем поэтапно добавлять к исключению информацию. В момент выкидывания исключения часть информации может быть недоступна; она может быть добавлена на верхних уровнях и т.д.


В строке //1 определяется описание пользовательской информации, которую можно сохранить в исключении. Первый аргумент error_info – это уникальный тип, второй – тип данных, который должен храниться в исключении (кстати, вы знали, что можно объявить структуру внутри шаблона? Я – нет).


В строке //2 выкидывается исключение, в котором сохраняется информация о файле.


В строке //3 к перехваченному исключению добавляется новая информация.


В //4 используется функция boost::diagnostic_information, которая формирует строку с описанием исключения и всей присоединенной к нему информации. Строка не особо человеко-читаема и пользователю ее лучше не видеть. Может быть полезно например для сохранения информации в лог.


В //5 происходит проверка на наличии в исключении данных errinfo_str и если они есть, выдается соответствующая строка.


Иерархия исключений


С учетом такой техники классы исключений становятся простыми "маркерами", не содержащими данных или методов. Каждый пользовательский класс исключений наследуется от boost::exception и возможно от std::exception. Чтобы пользовательские исключения могли использовать множественное наследование друг от друга, авторы библиотеки рекомендуют виртуальное наследование.


Удобно также завести свой собственный базовый класс исключений.


Получается примерно вот так:


1 struct MyException : virtual boost::exception, virtual std::exception
2 {
3 const char* what()const throw() { return boost::diagnostic_information_what(*this); }
4 };
5
6 struct MyFileException : virtual MyException {};
7 struct MyNetException : virtual MyException {};
8 struct MyComplexException : virtual MyFileException, virtual MyNetException {};

Использование


В библиотеке определен макрос BOOST_THROW_EXCEPTION, который выкидывает указанное исключение, добавляя к нему имя функции, имя и строку файла. На самом деле он не выкидывает исключение, а вызывает boost::throw_exception. Поведение последней настраивается опциями компиляции, по умолчанию она оборачивает исключение для возможности клонирования.


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


Впечатления


Библиотечка удобна в использовании, относительно невелика и понятно написана.


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


Предполагается что остальные части буста используют эту библиотеку для своих исключений, а так как функцию boost::throw_exception можно написать самому, то можно например встроить во все исключения стек-трейс. Однако к сожалению тот же boost::filesystem использует свой собственный макрос, который раскрывается в простой throw. В комментариях написано, что они отказались от boost::exception, так как она дает много лишней информации.

понедельник, 7 марта 2011 г.

Привет

Захотелось попробовать себя на ниве блоггерства.
Я - программист, пишу в основном на С++, немножко на Python и Delphi.


В этом блоге я буду описывать интересные мне проблемы программирования.

Через пару дней, если лень не загрызет, опишу свои недавние разбирательства с boost::exception.