29 марта 2011

Silent ;)

"Ленивые" логические операторы позволяют писать такой забавный код:
int nArgs = 0;
LPWSTR *Args = CommandLineToArgvW(GetCommandLineW(), &nArgs);

bool Silent = false;
if ((nArgs == 2) ||
((nArgs == 3) && boost::equals(Args[2], L"/silent") && (Silent = true)))
DoSomething(Args[1], Silent);

18 ноября 2010

(std::max)

Как-то давно я советовал бороться с макросами min/max из windows.h таким образом:
#include <windows.h>

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif
Напомню в чем суть проблемы: после включения windows.h уже нельзя писать std::max(a,b). Оказывается все гораздо проще: можно писать (std::max)(a,b). Препроцессор обойдет такую запись стороной. Тогда не нужны undef-ы (актуально если потом вдруг нужно включить что-нибудь типа atl*.h).

16 сентября 2010

Local functions in C++

В Pascal есть замечательная вещь - локальные функции. Иногда они ну ооочень удобны. Если кто с Паскалем не знаком, на C++ это бы выглядело примерно так:
int foo(int x)
{
int y = 0;

void bar(int z)
{
y += x*z;
}

bar(1);
bar(2);

return y;
}
То есть тело такой функции пишется внутри другой функции, и она может ссылать на ее переменные.
В C++ такого счастья нет. Может такого не сделали из-за того, что в Сях стек устроен немного по-другому, нежели в Паскале (в любом случае - не оправдание), может Бьярн счел это "не тру". Конечно, можно написать локальный класс, у которого будет нужная функция, но которому придется вручную передавать нужные переменные - но это неудобно, да и выглядит ужасно. Вот как-то так:
int foo(int x)
{
int y = 0;

class foobar
{
public:
bar(int &y, int x) : y(y), x(x)
{
}

void operator ()(int z)
{
y += x*z;
}

private:
int &y, x;
} bar(y, x);

bar(1);
bar(2);

return y;
}
Но с новым стандартом этот способ стал гораздо короче в записи:
int foo(int x)
{
int y = 0;

auto bar = [&](int z)
{
y += x*z;
};

bar(1);
bar(2);

return y;
}
Если сравнить с кодом, приведенным в начале - отличия минимальны! А уж если оно выглядит как утка, плавает как утка и крякает как утка - то чем не утка локальная функция?!

16 июля 2010

Mini HTTP server

Понадобился в приложении мини http сервер и http клиент. С клиентом все просто - можно просто воспользоваться функциями WinInet, написав к ним немного C++-оберток.

Сервер можно довольно просто написать на Boost.Asio, но оказалось все уже изобретено - хорошо подошла cpp-netlib, вот пример кода сервера из документации:
struct hello_world;
typedef http::server server;

struct hello_world {
void operator() (server::request const &request,
server::response &response) {
response = server::response::stock_reply(
server::response::ok, "Hello, World!");
}
void log(...) {
// do nothing
}
};

hello_world handler;
http::server server_("0.0.0.0", "80", handler);
server_.run();

Библиотека, правда, довольно сырая - пришлось немного допиливать напильником чтобы скомпилировалась под VC++10. Но зато в комплекте идут приятные бонусы типа url_decode, которые вполне пригодились.

PS. Забавно было когда сделал и запустил простейший сервер - FireFox грузил http://localhost/ очень долго, дольше чем http://www.yandex.ru/. Я уж было стал грешить на cpp-netlib и boost.asio, но оказалось IE открывал localhost мгновенно. Как я понял, FF долго ресолвит адрес localhost (причем каждый раз видимо заново), а http://127.0.0.1/ открывает мгновенно.

21 июня 2010

Lexical_cast using Spirit

Если хочется быстро заменить lexical_cast на парсеры из Spirit-а (для увеличения скорости парсинга, например):
template <class T>
inline T spirit_cast(std::string const & input)
{
T value;

std::string::const_iterator begin = input.begin();
bool result = boost::spirit::qi::parse(begin, input.end(), value);

if (!result || begin != input.end())
throw std::bad_cast();

return value;
};

11 мая 2010

News: good one and bad one

Новая Visual Studio 2010 огорчила: CRT поддерживает операционные системы не ниже WinXP SP2 и Win2003 SP1. Забудьте про Win2000 и даже WinXP без SP2. Это весьма печально.

А новый Boost 1.43 порадовал: наконец-то добавили RangeEx. Это очень радует. Да что уж там - это будет мой следующий любимый релиз после 1.39, где был добавлен ForEach. ;)

01 мая 2010

Comma operator returns

RAII в C++ - замечательная вещь. Количество кода сокращается на порядок (ну, если мыслить в двоичной системе исчисления, то в 2 раза - это на порядок, в 4 раза - уже на два порядка).

Например, следующий код дает нам возможность показать курсорчик "думаем..." и убрать его когда мы додумаем:
{
WTL::CWaitCursor WaitCursor;
SomeOperation();
}
Не нужно руками восстанавливать курсор ни в случае если операция завершится успешно, ни в случае если кинет исключение. Все сделает деструктор класса CWaitCursor.

Но вот такой случай заставляет нас вручную восстанавливать курсор:
{
WTL::CWaitCursor WaitCursor;
if (!SomeOperation())
{
WaitCursor.Restore();
MessageBox("не получилось :(");
}
}
Обидно, да? А не восстановим - получим MessageBox не с тем курсором.

Вот тут-то и приходит на помощь старый добрый оператор запятая. Трюк в чем: при вычислении выражения все его аргументы живут до конца вычисления всего выражения. Построим выражение, где у нас будет "думающий" курсор и наша операция:
if (WTL::CWaitCursor(), !SomeOperation())
MessageBox("не получилось :(");

09 марта 2010

Boost.Spirit in practice

Я заметил, у разработчиков совершенно полярное отношение к библиотеке Boost.Spirit: либо она им жутко не нравится, либо они фанатеют от нее. Конечно, описывать грамматику на C++ – занятие на любителя. Таким любителем оказался и я, когда познакомился со Спиритом. Хочу показать, как с помощью Спирита можно довольно просто решать повседневные задачи разбора текста.

Простая задача – как два пальца

На Спирите очень удобно писать маленькие парсеры «не отходя от кассы» – прямо в C++ коде. Вот например, как вы поступите если нужно распарсить строку вида «число-число», которая задает диапазон страниц для печати? На Спирите – одна строчка:

bool ok = parse(First, Last, (uint_ >> L".." >> uint_), MinMax) && (First == Last));

Посложнее…

Более того – можно ненамного сложнее создавать и парсеры побольше. В качестве примера рассмотрю парсер мини-языка, который я делал для API Яндекс.Бара. Задача была такова: для облегчения загрузки плагинов в баре используется XML, который довольно избыточный сам по себе. Но зато XML легче грузить из JavaScript-а, чем парсить произвольный формат (на JS пишутся расширения под FireFox, в том числе и Я.Бар).

Итак, что мне было нужно – имея на входе обычную инфиксную нотацию:

Hello * Interval * 60 + xpath("number(//hello[id='" # id # "')", World)

получить на выходе обычное AST в XML-формате:

<add>
<mul>
<value-of name="Hello"/>
<value-of name="Interval"/>
<value type="number">60</value>
</mul>
<xpath>
<concat>
number(//hello[id='<value-of name="id"/>')
</concat>
<value-of name="World"/>
</xpath>
</add>

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

root
child1: текст
@attribute1: text
@attribute2 = формула
grandchild
grand-grand-child
child2 = формула

То есть вложенность задается количеством табуляций, далее идет имя XML-ноды (элемента или атрибута). За ним - определенный символ, определяющий что идет далее: текст или формула. Текст нужно передавать на выход в «голом» виде, формулы – в виде AST.

Итого у меня два парсера – один разбирает строку чтобы получить имя ноды и текст или формулу. Второй – разбирает формулы, генерируя AST. Обработку количества табов я провожу снаружи старым добрым std::find_if.

Парсинг строки. Semantic actions – через Boost.Bind

Начну с более простого – разбора строк. Строки могут быть такие:

tag
tag: тест
tag = формула
= формула
name :: (instance|widget) (setting|variable)
name := формула

Парсер получается очень простой:

bool parse_definition(string::const_iterator &iter, string::const_iterator end, mini_xml &root)
{
qi::rule<string::const_iterator, string(), space_type> id, any_string, scope, type;
id %= raw[lexeme[-char_('@') >> alpha >> *(alnum | '_' | '-' | (':' >> alnum))]];
any_string %= lexeme[+char_];
scope %= raw[lit("widget") | lit("instance")];
type %= raw[lit("setting") | lit("variable")];

return phrase_parse(iter, end,
(
(id >> "::" >> scope >> type) [bind(&add_identifier, ref(root), _1)] |
(id >> ":=" >> any_string) [bind(&add_definition, ref(root), _1)] |
(id >> ':' >> any_string) [bind(&add_raw, ref(root), _1)] |
(id >> '=' >> any_string) [bind(&add_calculated, ref(root), _1)] |
( '=' >> any_string) [bind(&add_expression, ref(root), _1)] |
id [bind(&add_subnode, ref(root), _1)]
),
space) && (iter == end);
}

Использование phrase_parse() вместо parse() позволило мне переложить на Спирит обработку white space (пробелов, табуляций и т.п.) внутри выражений. Это позволит писать как «tag:text», так и «tag : text». Причем мой код, как видно, освобожден от обработки пробелов – все делает phrase_parse(). Мне остается только использовать lexeme[] там, где я хочу отключить такое поведение, и raw[] там, где я хочу получить исходный текст без вырезания пробелов.

Кстати, напомню что синтаксис правил у Spirit-а такой:

rule [semantic_action]

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

В моем случае на каждый тип строки – свое поведение, плюс в самом начале для упрощения последующего кода я ввел отдельные правила типа id, any_string. Код, вызываемый при соответствии строки определенному правилу – указан через лямбда-функции, создаваемые с помощью boost::bind. Синтаксис bind-а очень прост:

boost::bind(функция, аргумент, аргумент, ...)

В качестве аргументов можно указывать специальные placeholder-ы (вида _1, _2, …), указывающие куда вставлять аргументы лямбда-функции. На выходе каждого парсера получается одно значение, его и передаем в качестве аргумента нашей функции. Например, парсер

id >> '=' >> any_string

сгенерирует на выходе пару строк (в виде boost::fusion::vector<string, string>), которую я передаю в качестве второго параметра моей функции add_calculated, которая имеет такой вот интерфейс:

void add_calculated(mini_xml &root, fusion::vector<string, string> const &);

Первый параметр, который мне нужно передать этой функции – это ссылка на root, поэтому вызов boost::bind выглядит так:

bind(&add_calculated, ref(root), _1)

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

(id >> '=' >> any_string) [bind(&add_calculated, ref(root), _1)]

Парсинг формулы. Semantic actions – через Boost.Phoenix

Напомню какого вида функции мне нуно парсить:

Hello * Interval * 60 + xpath("number(//hello[id='" # id # "')", World)

При разборе формул могут встретиться:

  • числа
  • булевы константы (true, false)
  • строки (в кавычках)
  • идентификаторы
  • вызовы функций
  • операции

Для обработки результатов парсинга я создал один большой функтор и во всех семантических действиях использую его с помощью Booost.Phoenix. Как и у всех функторов, действия различаются не по именам, а по количеству и типам параметров.

struct compiler
{
// метки нужны для того, чтобы отличать друг от друга функции с одинаковыми аргументами
struct identifier{}; // метка «идентификатор»
struct function{}; // метка «функция»
struct parameter{}; // метка «параметр»
struct assignment{}; // метка «присваивание»

void operator()(mini_xml &node, std::string const &id, identifier) const; // идентификатор
void operator()(mini_xml &node, std::string const &id, function) const; // функция
void operator()(mini_xml &node, std::string const &id, parameter) const; // параметр функции
void operator()(mini_xml &node, std::string const &id, assignment) const; // присваивание
void operator()(mini_xml &node, std::string const &value, char const *type) const; // <value>
void operator()(mini_xml &node, mini_xml const &subnode) const;
void operator()(mini_xml &node, mini_xml const &subnode, std::string const &id, bool allow_join = false) const;
};

Внутри своей грамматики я добавил член класса - тот самый мой функтор:

template <typename Iterator>
struct expression_grammar : grammar<Iterator, mini_xml(), space_type>
{
expression_grammar() : expression_grammar::base_type(expressions)
{
expressions = ...;
}

rule<Iterator, mini_xml(), space_type> expressions, ...;
boost::phoenix::function<compiler> op;
}

PS. Тип mini_xml – это генерируемый XML.

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

id %= raw[lexeme[alpha >> *(alnum | '_' | ('-' >> alnum))]];
quoted_string %= lexeme['"' >> *(char_ - '"') >> '"'];
numeric_value %= raw[lexeme[-(char_('+') | char_('-')) >> +digit >> -(char_('.') >> +digit)]];
boolean_value %= raw[lit("true") | lit("false")];

Все эти правила на выходе выдают строку (например, название идентификатора). Оператор %= в конструкции “правило %= парсер” позволяет сгенерированное парсером значение передать прямо на выход парсера. Далее можно прямо в других правилах использовать их результаты:

string = quoted_string [op(_val, _1, "string")];
number = numeric_value [op(_val, _1, "number")];
boolean = boolean_value [op(_val, _1, "bool")];
empty = lit("empty") [op(_val, std::string(), "empty")];
identifier = id [op(_val, _1, compiler::identifier())];

Как видно, здесь в каждом случае вызывается парсер, например, quoted_string, а далее его значение используется для вызова функтора op. В первом случае (правило string) на вход функтора придет: в качестве первого аргумента – то значение, которое формируется (в моем случае – элемент дерева XML), в качестве второго – результат работы парсера quoted_string, в третьем – срока “string”. И уже функтор сделает все необходимые действия с XML-деревом.

Определение функции не намного сложнее – в частности брагодаря тому, что я генерирую XML. Параметры функции достаточно просто «прикрепить» к xml-узлу функции в качестве «детей»:

function =
id [op(_val, _1, compiler::function())]
>> '('
>> -(parameter [op(_val, _1)] % ',')
>> ')';

Выражение «parameter [op(_val, _1)]» как раз прикрепляет детей к функции: в функтор op передается родитель (узел функции, который только что заполнен с помощью «op(_val, _1, compiler::function())») и «ребенок» (узел параметра, который сгенерировал парсер parameter).

Итого, без учета бинарных и тернарных операций (операций с 2 и 3 аргументами, такие как */+-?:) получается следующее правило:

factor =
function [_val = _1]
| boolean [_val = _1]
| empty [_val = _1]
| identifier [_val = _1]
| string [_val = _1]
| number [_val = _1]
| ('(' >> expression [_val = _1] >> ')')
| (lit('!') [op(_val, "not", compiler::function())] >> factor [op(_val, _1)])
| (lit('-') [op(_val, "neg", compiler::function())] >> factor [op(_val, _1)])
| ('+' >> factor [_val = _1])
;

При обработке операций не следует забывать об их приоритете. Его легко реализовать «вкладывая» определения одной операции в определение другой:

addition =
multiplication [_val = _1]
>> *( ('+' >> multiplication [op(_val, _1, "add", true)])
| ('-' >> multiplication [op(_val, _1, "sub", true)])
)
;

multiplication =
factor [_val = _1]
>> *( ('*' >> factor [op(_val, _1, "mul", true)])
| ('/' >> factor [op(_val, _1, "div", true)])
)
;

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

Суммируя все вместе

Весь исходный код можно взять здесь: http://download.yandex.ru/bar/tools/easierxb-src.zip (внутри архива – проект для сборки под Windows и MacOS).

Пример входного файла: http://download.yandex.ru/bar/tools/easierxb-example.zip

26 декабря 2009

Fast iterating over xml nodes by MSXML

Волею судеб мне приходится иметь дело с MSXML. Не самая лучшая библиотека для работы с xml, но так вот получилось. И будучи любителем конструкции for_each, обход дочерних нод в моем коде выглядит так:
BOOST_FOREACH(IXMLDOMNodePtr Node, ChildNodes(RootNode))
DoSomething(Node);
ChildNodes отдает пару итераторов, которые дают доступ по IXMLDOMNodeList через IXMLDOMNodeList::item(). Так вот на обработке большого xml файла заметил, что такая конструкция очень жутко тормозит. Я было хотел списать все это со словами "ну этож MSXML тормозная - ничего не поделаешь...". Но как оказалось, правдива только первая часть этой фразы, а "поделать" все-таки что-то можно: если перебирать ноды через IXMLDOMNodeList::nextNode(), то вместо исходных 450мс получалось обойти ноды всего за 28:
IXMLDOMNodeListPtr NodeList = RootNode->childNodes;
for (IXMLDOMNodePtr Node; Node = NodeList->nextNode(); )
DoSomething(Node);

Видимо про такие случаи Спольски рассказывал байку про маляра, который красил забор. За первый день он покрасил 20 метров забора, за второй - 10, за третий - всего 2 (цифры привожу по памяти, память дырявая). Когда его спросили, почему он так стал тормозить, ответ был великолепен: "Насяльника, так за краской все дальше и дальше ходить!" Видимо IXMLDOMNodeList::item() работает таким же образом, как и тот маляр, так что beware!

02 декабря 2009

Calculating XPath expressions by MSXML

Если посчитать выражение XPath, что может получиться?
Согласно спецификации XPath:
- список узлов
- строка
- логическое значение
- число

В .NET-е можно получить все из вышеперечисленного. А вот MSXML предлагает API только для получения списка узлов - selectNodes.

А что, если нужно получать значения других типов? Товарищи в форумах предлагают хитрый трюк - обернуть XPath выражение в XSLT, наложить его на нужный узел или документ, а в результате получить строку. Даже закрывая глаза на производительность такого трюка, таким способом не удается получить всю информацию - а именно информацию о типе результата. Ведь на выходе - всегда строка.

Если выражение - "наше", то тип результата сразу знаем. Но если "чужое", и нам нужно узнать тип результата, то в таком случае мы в тупике - из полученной строки не получится извлечь тип. Вот, 123 - это строка "123" или число 123?

Я узнал о следующем - в MSXML можно использовать JavaScript внутри XSLT. Тогда все становится достаточно просто:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:js="urn:custom-javascript"
exclude-result-prefixes="msxsl js" >
<xsl:template match="/">
<xsl:variable name="value" select="выражение"/>
<result>
<value>
<xsl:value-of select="$value"/>
</value>
<type>
<xsl:value-of select="js:TypeOf($value)"/>
</type>
</result>
</xsl:template>
<msxsl:script language="JavaScript" implements-prefix="js">
function TypeOf(strText) { return typeof strText; }
</msxsl:script>
</xsl:stylesheet>
Специальная JavaScript-функция вернет нам тип выражения, остается только не думать какая у всего этого производительность ;)

02 ноября 2009

Unicode: life-hack

Мой обычный сценарий при создании новых файлов в проекте:
1. right click в Windows explorer на папке, где нужно создать файл
2. выбор Create/"New Text File.txt"
3. ввод имени файла
4. right click на файле
5. SVN/Add
6. Перетаскивание файла в IDE

Небольшой лайф-хак позволяет сразу создавать файлы в UTF-8:

Файл создается с BOM для UTF-8.

21 октября 2009

Yo!

Отсортировать строки по алфавиту очень просто - все необходимые сведения о порядке символов есть в классе std::locale. Остается только надеяться что std::locale не подведет. А, как оказалось, она может. Вот пример:
struct lexicographical_order
{
std::locale Locale;

lexicographical_order(std::locale Locale) : Locale(Locale)
{
}

bool operator()(std::wstring const &lhs, std::wstring const &rhs) const
{
return boost::ilexicographical_compare(lhs, rhs, Locale);
}
};

int main()
{
std::vector v;
v.push_back(L"яблоко");
v.push_back(L"апельсин");
v.push_back(L"банан");
v.push_back(L"персик");
v.push_back(L"ёжик");

std::locale Locale(""); // используем текущую локаль системы
std::cout << Locale.name() << std::endl;
std::sort(v.begin(), v.end(), lexicographical_order(Locale));

_setmode(_fileno(stdout), _O_U16TEXT);
std::copy(v.begin(), v.end(), std::ostream_iterator(std::wcout, L"\n"));
return 0;
}

Под русской Windows получаем:
Russian_Russia.1251
ёжик
апельсин
банан
персик
яблоко

Буква "ё" явно пользуется у данной локали большей популярностью, чем все остальные. Ну прям хоть свою локаль пиши...

28 августа 2009

Boost Tuple Serialization

Сложно поверить, но в Boost-е нет сериализации для tuple.

Видимо вся фигня в том, что при сериализации кортежей нужно написать сериализацию для кортежей 0..N элементов, а это можно сделать:
а) руками: написать вручную сериализацию для 0, 1, 2 и т.д. элементов, что совсем не кошерно;
б) препроцессором: использовать boost preprocessor - тоже считается не очень кошерно;
в) компилятор: но тут вся загвоздка как я понял в том, что это нельзя сделать используя публичный интерфейс tuple, можно только зная особенности реализации (details), что тоже вроде бы не очень кошерно.

В результате был выбран списоб "г" - вообще не писать сериализацию для tuple. Что я считаю полным бредом.

PS. Если что - есть готовая реализация через препроцессор.

19 июля 2009

Sorting by several fields

Как вы сортируете по нескольким критериям?
struct foo
{
A a; B b; C c; D d;
X x; Y y;
};

// нужно отсортировать по a, затем по b, затем по c, ну а затем еще и по d
// по x и y сортировать не нужно
bool s(foo const &l, foo const &r)
{
if (l.a < r.a) return true;
if (l.a > r.a) return false;
if (l.b < r.b) return true;
if (l.b > r.b) return false;
if (l.c < r.c) return true;
if (l.c > r.c) return false;
return l.d < r.d;
}

std::vector<foo> f;
std::sort(f.begin(), f.end(), s);

Признаюсь, я примерно так раньше и сортировал. Хотя по сути-то [a,b,c,d] - это ж по-сути обычный кортеж, а для них уже есть готовые библиотеки. И операции сравнения кортежей там должны быть, в этих библиотеках. Нужно просто загнать l и r в кортежи (только для скорости - не в [A,B,C,D], а в [const&A,const&B,const&C,const&D]), и сравнить:
#include <boost/tuple/tuple_comparison.hpp>

bool s(foo const &l, foo const &r)
{
return boost::tie(l.a, l.b, l.c, l.d) < boost::tie(r.a, r.b, r.c, r.d);
}

16 июля 2009

Static member definition

Интересно, что статический член класса нельзя определить прямо в декларации класса:
class foo
{
static std::string s('hello world');
};
А иногда сильно этого хочется - например в декларации шаблона. Или просто в каком-нибудь header-only классе. Самое интересное, что внутри функции такое провернуть можно. Поэтому можно сделать финт ушами - сделать доступ к статическому члену через member-функцию:
class foo
{
static std::string &s()
{
static std::string impl('hello world');
return impl;
}
};
Синтаксис при обращении немного поменяется - s() вместо s, но часто оно того стоит.

23 июня 2009

Fast parsing

Чем вы парсите всякую "мелочёвку" типа целочисленных значений и прочее? Самописными парсерами с приправами вроде boost::tokenizer, boost::lexical_cast или даже atoi, etc.? Я обычно использовал boost::regex + lexical_cast. А тут недавно посмотрел презентацию Spirit-а с BoostCon-а, даже немного проникся. Раньше как-то я к Спириту более равнодушен был.

Описывать грамматику на C++ и потом давать компилятору [несколько минут] это компилировать - это, конечно, большой изврат. Но весьма забавно. И, говорят, работает потом очень быстро. Даже если нужно просто число распарсить - говорят быстрее всех парсит.
std::string buffer("1234");
int value = 0;

using namespace boost::spirit;
bool r = qi::parse(buffer.begin(), buffer.end(), int_, value);
assert(r && value == 1234);

14 июня 2009

Visual Studio debug visualizers

Начиная с Visual Studio 2005 можно писать свои правила для отображения значений в отладчике. Более подробнее об этом можно почитать в блоге virtualdub.

Правила для Boost-овских типов можно найти на Boost Wiki. Единственное, не нашел там то, что искал — для boost::variant. Набросал на коленке:
boost::variant<*,*,*,*,*,*,*,*,*,*,*> {
preview (
#(
#switch($c.which_)
#case 0 ( *($T1 *)&($c.storage_.data_) )
#case 1 ( *($T2 *)&($c.storage_.data_) )
#case 2 ( *($T3 *)&($c.storage_.data_) )
#case 3 ( *($T4 *)&($c.storage_.data_) )
#case 4 ( *($T5 *)&($c.storage_.data_) )
#case 5 ( *($T6 *)&($c.storage_.data_) )
#case 6 ( *($T7 *)&($c.storage_.data_) )
#case 7 ( *($T8 *)&($c.storage_.data_) )
#case 8 ( *($T9 *)&($c.storage_.data_) )
#case 9 ( *($T10 *)&($c.storage_.data_) )
)
)
children
(
#(
value:
#switch($c.which_)
#case 0 ( *($T1 *)&($c.storage_.data_) )
#case 1 ( *($T2 *)&($c.storage_.data_) )
#case 2 ( *($T3 *)&($c.storage_.data_) )
#case 3 ( *($T4 *)&($c.storage_.data_) )
#case 4 ( *($T5 *)&($c.storage_.data_) )
#case 5 ( *($T6 *)&($c.storage_.data_) )
#case 6 ( *($T7 *)&($c.storage_.data_) )
#case 7 ( *($T8 *)&($c.storage_.data_) )
#case 8 ( *($T9 *)&($c.storage_.data_) )
#case 9 ( *($T10 *)&($c.storage_.data_) )
)
)
}

23 мая 2009

Container with a preallocated buffer

STL-ные контейнеры - замечательная вещь, вот только хранят они все на куче. А иногда ведь так хочется, чтобы они использовали какой-нибудь буфер внутри объекта. Что-то типа такого:
template <typename T, size_t N>
struct Container
{
size_t size; // сколько элементов в контейнере
T storage[N]; // заранее выделенный буфер для объектов
};
Такой контейнер будет полезен, например, когда создается временный контейнер на стеке, максимальное количество элементов в нем предполагается известным. Или, например, когда нам нужен контейнер на небольшое нефиксированное количество элементов. Ну правда, стоит ли из-за контейнера на 1-2 небольших элемента заниматься выделением-освобождением памяти на куче (что довольно затратно по времени)?

Есть похожие на это контейнеры:
- boost.array, но он хранит только фиксированное количество элементов, для переменного количества не подойдет
- boost.optional - это почти то, что нужно, но если нужно хранить не более 1 элемента ;)

Нужный мне контейнер есть в стандартной библиотеке C++, которая поставляется с Visual C++, называется он basic_string. В этой реализации (возможно и в каких-то других) он имеет буфер на небольшое количество символов - чтобы для небольших по размеру строк не лазить на кучу. Однако, размер буфера там не регулируется "снаружи", да и нет смысла полагаться на конкретную реализацию - в другой стандартной библиотеке может быть все по-другому, да и эта может "изменить" в любой момент.

В boost-е планируется к review нужный контейнер - auto_buffer. Его еще немного допилят по интерфейсу, но пользоваться уже можно смело. В добавок он может, как и basic_string, расти в кучу, если нужно.

08 апреля 2009

Range concatenation

В библиотеках для работы с итераторами (Boost.Iterators, Boost.Range, RangeEx) куча полезных вещей, но почему-то нигде не нашел механизма "склеить" два диапазона в один. Такого, чтобы можно было сделать вот так:
template <class Range> void process_range(Range const &);

std::vector<foo> v = ...;
std::list<foo> l = ...;
process_range(concat(v, l));
Вот разбить диапазон на части - пожалуйста, перемещать значения в случайном порядке - да не вопрос, а вот склеить - нифига.

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

С использованием boost::variant все получилось достаточно компактно, хотя не максимально эффективно:
template <class TIterator1, class TIterator2, ...>
class ConcatIterator : public boost::iterator_facade<...>
{
public:
reference dereference() const
{
return boost::apply_visitor(DereferenceVisitor<reference>(), It);
}

void increment()
{
boost::apply_visitor(IncrementVisitor(), It);
StabilizeForward();
}

void decrement()
{
StabilizeBackward();
boost::apply_visitor(DecrementVisitor(), It);
}

bool equal(const ConcatIterator& lhs) const
{
return (Range == lhs.Range) && (It == lhs.It);
}

private:
size_t Range; // 0 for the first range, 1 for the second range
boost::variant<TIterator1, TIterator2> It, End1, Begin2;
};

PS. Нет худа без добра. Мой велосипед вдохновил разработчиков Boost.Range добавить объединение диапазонов в RangeEx.