08 августа 2008

String parameters

Пытался установить Visual Studio 2005 Service Pack 1 на машине, у которой всего полтора гигабайта свободного места на диске. Думаете получилось? Нифига - места на диске не хватило!

Этот сервис пак представляет собой .exe размером 500Mb, внутри которого .msp, а внутри .msp видимо все обновляемые файлы лежат. То сначала распаковывается содержимое .exe в Temp, потом туда же распаковывается содержимое .msp, и видимо там дальше еще что-то распаковывается... Вобщем, красота.

К чему это я - к тому как часто передаются строки в качестве параметров. Строка в C++ - чаще всего std::basic_string:
void foo(const std::string& Param);
Если у нас строка хранится не в std::string, а например в const char*, то получим неявное копирование строки в конструкторе std::string.

void foo(const char* Param);
Уже лучше - можем передать string.c_str(), можем что угодно другое, где есть null terminator. А если у нас строка (или даже большой текст) загружен из ресурсов и есть const char* data() и size_t size(), но нет замыкающего нуля - снова придется делать копию. И это не единственный вариант, когда есть data()/size() - если хотим передать подстроку (внутреннюю часть какой-либо большой строки), то возникнет та же проблема.

void foo(const char* Param, size_t Size = strlen(Param));
Уже намного лучше. Правда если строка в другой кодировке или вообще, читается с потока - опять нужно складывать копию в память, а потом только вызывать foo().

А хорошая альтернатива - это парочка итераторов (a-la STL) или контейнер (a-la Boost):
template <typename T>> void foo(T First, T Last);
template <typename T>> void foo(const &T String);

Конструкции почти эквивалентны, т.к. у контейнера легко можно взять String.begin() и String.end(), а пару итераторов - скормить boost::make_iterator_range(). Так что скорее вопрос вкуса, какой вариант выбрать - я за контейнер.

С таким интерфейсом мы можем взять свести копирование к минимуму. Например, пришли данные в urlencode, да еще в win1251:
char* data = "q=%EF%F0%E8%E2%E5%F2&a=%EC%E5%E4%E2%E5%E4";

Парсим все в указатели:
typedef std::pair<char*, char*> substr;
typedef std::pair<substr, substr> item;


Делаем итератор для декодирования urldecode ("%EF%F0%E8%E2%E5%F2" -> "привет") - и его operator* и operator++ будут делать всю работу "на ходу". (Тут можно призвать на помощь boost::iterator_facade). Нужно конвертировать в utf-8? - еще один итератор ;) И все это скормим нашей foo().

PS. Подробности такого подхода - в boost.iterator, boost.range, boost.string algo.

05 августа 2008

Easy double-buffering

Типичный код отрисовки окна:
LRESULT CMyWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CRect ClientRect;
GetClientRect(ClientRect);
WTL::CPaintDC DC(m_hWnd);

DrawBackground(DC, ClientRect);
DrawContent(DC, ClientRect);
...
}
Если все это перерисовывается довольно часто, то возникает мерцание. Например при изменении мышкой размера окна — на каждое движение мыши идет перерисовка, сначала рисуется фон, потом контент, легко застать момент когда фон отрисовался, а остальное - нет.

Типичный паттерн чтобы этого избежать — рисовать во время обратного хода луча использовать double buffering. Думаете, это сложно? На самом деле — как два пальца. Понадобится всего лишь небольшой вспомогательный класс:
LRESULT CMyWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CRect ClientRect;
GetClientRect(ClientRect);
WTL::CPaintDC PaintDC(m_hWnd);
CMemDC DC(PaintDC, ClientRect);

DrawBackground(DC, ClientRect);
DrawContent(DC, ClientRect);
...
}

Сам "помощник" выглядит так:
class CMemDC : public WTL::CDC
{
public:
CMemDC(HDC PaintDC, const RECT &Area) : _PaintDC(PaintDC), _Area(Area)
{
CreateCompatibleDC(PaintDC);

POINT Viewport;
::GetViewportOrgEx(PaintDC, &Viewport);
SetViewportOrg(Viewport);

_Bitmap.CreateCompatibleBitmap(PaintDC, _Area.Width(), _Area.Height());
SelectBitmap(_Bitmap);
};

~CMemDC()
{
POINT OriginalViewport;
SetViewportOrg(0, 0);
_PaintDC.SetViewportOrg(0, 0, &OriginalViewport);
_PaintDC.BitBlt(_Area.left, _Area.top, _Area.Width(), _Area.Height(), m_hDC, 0, 0, SRCCOPY);
_PaintDC.SetViewportOrg(OriginalViewport.x, OriginalViewport.y);
}

private:
WTL::CBitmap _Bitmap;
WTL::CDCHandle _PaintDC;
CRect _Area;
};

25 июля 2008

Microsoft's APIs: features, not bugs

У Microsoft если что-то работает не так, как ожидалось - это не баги, а фичи. Они документируются и их не чинят.

Мне (и другим тоже) почему-то казалось, что функция AplhaBlend() должна брать обычное сырое (raw) RGBA изображение и рисовать его с учетом прозрачности. И я долго не мог понять, почему точки с A=0 и с A=255 рисуются нормально, а все остальное - неправильно (получается очень белое).

Оказывается, нужно заранее самостоятельно наложить альфа-канал на картинку, типа такого:
bgra_pixel *It(Bits);
for (int Left = Width * Height; Left; --Left, ++It)
{
It->blue = ((unsigned)It->blue * It->alpha) >> 8;
It->green = ((unsigned)It->green * It->alpha) >> 8;
It->red = ((unsigned)It->red * It->alpha) >> 8;
}

19 июня 2008

Boost.Range for null-terminated strings

Оказывается то, что boost::begin()/end()/size() перестали работать с char*/wchar_t* - это фича, а не баг. Все дело оказалось вот в чем:
const char nullterminated[] = "hi"; // sizeof(nullterminated) == 3
const char[2] array = { "h", "i" }; // sizeof(array) == 2
boost::size(nullterminated) == boost::size(array) ?;
В таком случае boost::size() не может догадаться есть ли у массива null terminator или нет, поэтому решили отдать этот вопрос "пользователям", введя as_literal/as_array.

То есть теперь только так: boost::size(boost::as_literal("hello"));

18 июня 2008

Boost 1.35 issues

Boost 1.35 меня убивает. То, что при компиляции моих проектов появились варнинги там где их раньше не было - это меньшее из зол. Тут столкнулся со следующим: Boost.Range перестало работать с null terminated strings.

Вот такой вот код компилируется и работает с 1.34.1 и не компилируется 1.35:
const char* String = "hello";
size_t Len = boost::end(String) - boost::begin(String);

PS. Для меня было откровением узнать, что в NTFS есть hard- и symbolic links. Можно создать папочки boost_1_35 и boost_1_34_1 и символическую ссылку boost на одну из них. При необходимости можно легко поменять ссылку.

17 июня 2008

Referrer in Internet Explorer

Оказывается, Internet Explorer не передает referrer-а при открытии страниц в новом окне или вкладке. На открытой странице в javascript-е можно узнать referrer-а через window.opener.location. Однако, если попытаться получить window.opener через IHTMLWindow2::opener, то получим фиг нулевой указатель.

Попробовал через IHTMLWindow2::execScript выполнить SomeTempVar = window.opener.location.href (execScript не возвращает значения, приходится извращаться через временную переменную) - та же фигня.

Пришлось ловить NewWindow3 (а оно требует минимум IE6 в WinXP SP2), запоминать кто кого открыл, а потом "вспоминать" в другом экземпляре WebBrowser-а.

Поражаюсь разработчикам Internet Explorer-а.

22 мая 2008

Smart pointer for COM objects

Указатели на объекты должны быть умными - чтобы автоматически управлять временем жизни объекта. Это понятное дело. Для COM объектов есть широкий выбор стандартных умных указателей: _com_ptr_t, CComPtr, CComQIPtr. Какой же из них предпочесть? Большой разницы между ними нет, однако вся соль - в нюансах.

Шаблоны CComPtr и CComQIPtr предлагает нам ATL. Так что ести вы не используете ATL у себя в проекте, то ваш выбор однозначен - _com_ptr_t. Тут даже думать не надо - особого смысла пораждать зависимость от ATL ради CComPtr/CComQIPtr нет.

У _com_ptr_t есть полезный макрос для создания конкретных типов (через typedef): _COM_SMARTPTR_TYPEDEF. Выглядит это так:
// = typedef _com_ptr_t<...> IFooBarPtr;
_COM_SMARTPTR_TYPEDEF(IFooBar, __uuidof(IFooBar));
...
IFooBarPtr FooBar;
Еще бОльшая прелесть состоит в том, что для большинства интерфейсов уже заданы такие typedef-ы, то есть можно смело писать IHTMLDocument2Ptr Document; без соответствующего тайпдефа. Однако засада в том, что сделаны не все тайпдефы и для некоторых интерфейсов придется либо самим объявлять тип через _COM_SMARTPTR_TYPEDEF или использовать _com_ptr_t напрямую.

_com_ptr_t, как и CComQIPtr умеют вызывать QueryInterface "на ходу", что довольно удобно:
IDispatchPtr RawDocument = GetDocument();
IHTMLDocument2Ptr Document(RawDocument); // здесь автоматически вызывается QueryInterface
Небольшим плюсом CComQIPtr может служить его бОльшая лаконичность по сравнению с _com_ptr_t:

CComQIPtr<IHTMLDocument4> Document;
vs
_com_ptr_t<IHTMLDocument4, &__uuidof(IHTMLDocument4)> Document;

Вывод из этого я делаю такой: если используете ATL — берите CComQIPtr, если нет — _com_ptr_t. Если вдруг придется "слезть" с ATL, то будет довольно просто перевести код с CComQIPtr на _com_ptr_t.

Post scriptum
Все перечисленные выше умные указатели переопределяют operator& так, что он возвращает адрес обычного указателя, вместо адреса умного указателя. Это очень удобно для таких вот вызовов, которых в COM предостаточно:
IWebBrowserPtr Browser = GetBrowser();
IDispatchPtr Document;
// calling HRESULT STDMETHODCALLTYPE get_Document(IDispatch **ppDisp);
Browser->get_Document(&Document);
Соответственно, если будет желание положить элемент в контейнер, то оператору & очень желательно вернуть прежнюю логику. Например, обернув умный указатель в CAdapt из ATL:
std::vector< CAdapt< CComQIPtr<IDispatch> > > Objects
А если понадобится, то можно в любой момент сделать "срезку" до CComQIPtr/_com_ptr_t с его переопределенным оператором &:
BOOST_FOREACH(CComQIPtr<IDispatch> &Object, Objects)
Browser->get_Document(&Object);

PPS. Дискуссии по теме: здесь и здесь.

20 мая 2008

Naming arguments in constructors

Кстати, имена аргументов в конструкторе вполне могут совпадать с именами членов:
class foo
{
foo(int x, int y) : x(x), y(y) {}
int x, y;
};

16 мая 2008

ActiveX controls registration issues

ATL дает довольно удобные средства для создания ActiveX control-ов: стандартный визард вообще весь необходимый код генерирует сам. С проблемами можно столкнуться только при регистрации контролов из под кода, выполняющегося без администраторских прав.

Что можно предпринять в этом случае? На этот случай есть хороший выход - per user component registration: если регистрироваться не в HKEY_CLASSES_ROOT (который на запись мапится в HKEY_LOCAL_MACHINE\SOFTWARE\Classes), а в HKEY_CURRENT_USER\Software\Classes. для этого можно:
а) поправить .rgs файлы, сгенерированные визардом,
б) использовать перенаправление реестра.
Второй вариант хорош тем, что можно это делать по условию "запущены ли мы под админскими правами":
if (!IsAdmin())
{
HKEY hKeyCurrentUser;
RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Classes"), 0, KEY_ALL_ACCESS, &hKeyCurrentUser);
RegOverridePredefKey(HKEY_CLASSES_ROOT, hKeyCurrentUser);
RegCloseKey(hKeyCurrentUser);
}
Облом состоит в том, что в регистрации библиотек типов под Вистой есть ошибка, которая не схватывает перенаправление реестра. Предлагается поставить Service Pack и вызвать хитрую функцию из хитрой библиотеки. Но это не наш путь, требовать всяких сервис паков. В результате регистрацию библиотек типов нужно делать вручную (точнее полу-автоматом - написав .rgs файл), отключив при этом стандартную регистрацию параметром FALSE в выховах _AtlModule.DllRegisterServer() и _AtlModule.DllUnregisterServer().

Но все эти труды будут напрасны, если ваш ActiveX control устанавливается Internet Explorer-ом под Вистой. Он устанавливает компоненты только под админскими правами, то есть если у пользователя их нет - вылезет окно UACа :(

26 апреля 2008

Speeding up compilation

Компиляция C++ проектов - вещь довольно затратная по времени. Что можно предпринять для ускорения? Мои советы такие:

1. Разбивайте проект на отдельно компилируемые файлы - тогда при изменении одного фрагмента системы перекомпилируется только этот файл. Тут, правда, палка о двух концах - возрастет время rebuild-а, но полный ребилд делается обычно реже чем фазы "немного поправил - скомпилировал".

2. Включайте (#include) только самое необходимое. Иногда достаточно включить some_forward.h или some_feature1.h вместо большого some.h. Для использовании ссылки или указателя на класс компилятору не нужно знать все о классе - пользуйтесь этим при написании header-файлов:
//вместо #include "SomeClass.h"
class SomeClass;

void Foo(SomeClass&);
void Bar(SomeClass*);
А шаблоны вообще раскрываются в момент использования, а не декларации.

3. Распараллеливайте компиляцию на несколько процессоров.

4. Тривиально: поставьте побольше оперативной памяти.

5. Во время компиляции запустите system monitor и убедитесь, что не только процессор и память имеют значение. Узкое место: жесткий диск. Поставьте два три четыре быстрых диска в RAID0. Я серьезно.

20 апреля 2008

Linkdumps from DOU

На developers.com.ua еженедельно раздают замечательные подборки ссылок. Можно подписаться через общий rss, а можно отфильтровать по слову "linkdump".

06 апреля 2008

Accessing old-school APIs as C++ containers

Часто API и библиотеки предоставляют доступ к набору объектов в следующем виде:
size_t GetItemCount();
value GetItem(size_t Index);
Особенно это относится к библиотекам на C. Оказалось, очень просто преобразовать такой интерфейс к более стандартному для C++ виду - итераторам:
class iterator : public boost::iterator_facade<iterator, value,
std::random_access_iterator_tag, value, size_t>
{
public:
iterator(size_t Index) : Index_(Index) { }

// implementation
public:
reference dereference() const { return GetItem(Index_); }
void increment() { ++Index_; }
void decrement() { --Index_; }
bool equal(const iterator& It) const { return Index_ == It.Index_; }

private:
size_t Index_;
};
Теперь можно делать STL-way:
std::copy(iterator(0), iterator(GetItemCount()), ...);
Или даже так:
BOOST_FOREACH(value v, make_iterator_range(iterator(0), iterator(GetItemCount())))
...;

30 марта 2008

Boost 1.35

Вышел очередной Boost. Много нового. Собирать с опцией --build-type=complete.

22 февраля 2008

Coding Standards

По ссылке с DOU: coding standards одного проекта. 141 страница! Очень грамотно что помимо самого правила описывают и причину его введения.

18 февраля 2008

Passing values to a function

"Стандартный" способ передать N значений типа T на C:
void foo(T* values, int N);
Стандартное решение для C++:
template <typename it_t>
void foo(it_t first, it_t last);
Ну, если у нас есть vector<string> и foo() работает с string - то все тривиально. Засада если у нас все лежит как-нибудь не так, например в map<int,string> или в каком-нибудь vector<Object>, у которого нужно вызвать Object::GetString() для каждого элемента. А если еще и не все значения подряд нужны?

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

Тут стоит вспомнить, что умные дядьки уже подумали о нас, написали нужные адаптеры для итераторов, и запихнули все это в boost. Так, transform iterator достанет нам строку из pair<int,string> или Object::GetString(), а filter iterator отфильтрует нам элементы на ходу.

14 февраля 2008

std::equal_to

С удивлением обнаружил, что std::equal_to и сотоварищи объявлены так:
template<class _Ty>
struct equal_to
: public binary_function<_Ty, _Ty, bool>
{ // functor for operator==
bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator== to operands
return (_Left == _Right);
}
};

Я-то наивно полагал, что должно быть как-то так:
template<class T1, class T2 = T1>
struct EqualTo : public std::binary_function<T1, T2, bool>
{
bool operator()(typename boost::call_traits<T1>::param_type Left,
typename boost::call_traits<T2>::param_type Right) const
{
return Left == Right;
}
};

08 февраля 2008

Multithreading: Messages

Хорошие языки учат хорошим манерам, но это не мешает использовать эти манеры не только в этих самых хороших языках. (Умные указатели тому пример.)

Многопоточность в C++ - довольно нетривиальная вещь, так как сам язык не дает (пока) абсолютно ничего для этого. Типичная подход - защита совместно используемых объектов критическими секциям. При неаккуратном обращении с ними недалеко до простоя потоков, а иногда и до deadlock-ов.

Как в свое время все вызовы new/delete упаковали в умные указатели, можно так же поступить и с критическими секциями, оставив для общения потоков такой вот простой интерфейс:
template <typename T>
void SendMessage(Thread, std::auto_ptr<T> Message);
(можно отказаться от шаблона в пользу IMessage вместо T - это на свой вкус и цвет).

Послали сообщение - и бежим дальше, никаких лишних простоев.

PS. Почему auto_ptr, а не копия объекта: не всегда нужна копия, часто сообщение готовится только для того чтобы быть посланным - нет смысла делать оверхед в виде копии. А уж если нужна копия, то все просто:
SendMessage(SomeThread, Сlone(Message));
(или Message.Сlone(), у кого как)

05 февраля 2008

Quick way or right way

Бывало ли у вас так, что можно сделать:
а) как проще
б) как правильно?

У меня - постоянно ;) Примеров - хоть отбавляй. Одна из наиболее частых ситуаций: добавление нового атрибута (member-а) к объекту.

Как это выглядит:
class Object { ... };

class ObjectUser1
{
void UseObject(Object&);
Object ReturnObject();
std::container<Object> SomeMember_;
};

class ObjectUser2...

И тут для конкретной задачи UserOfObjectUser1 требуется чтобы Object еще имел, например, и атрибут Name:
class Object
{
... здесь то, что было...

// добавляем
void SetName(NameType);
NameType Name() const;
NameType Name_;
};
Хочется думать, что Name - очень полезный атрибут для Object, и он ему совсем не помешает. Хотя предыдущим ObjectUser-ам (и user-ам этих user-ов) он вообще не нужен, но они при новой компиляции ничего и не заметят. А думать так хочется потому как альтернатива этому - такова:
class ObjectWithName : public Object
{
ObjectWithName(A a) : Object(a) {}
ObjectWithName(B b, C c) : Object(b, c) {}
ObjectWithName(D d) : Object(d) {}
...
... сколько еще там у Object конструкторов?
... и не забыть добавить сюда новый конструктор если появится у Object
...
ObjectWithName(const Object& o) : Object(o) {}

void SetName(NameType);
NameType Name() const;
NameType Name_;
};
Да еще ObjectUser1 придется делать шаблонным - чтобы мог работать как с Object, так и с ObjectWithName.

Но если сделать как проще, а не как правильно - последствия дадут знать о себе потом. Вот пример:
class File
{
string Name;
int Size;
vector DiskSectors;
void Read(...);
void Write(...);
}
Хотя файл по определению - именованная область на диске, но Name явно здесь лишнее. Точнее - лишнее все, кроме Name. Не верите? Попробуйте создать hard link ;)

Истина где-то рядом:
class DiskArea
{
int Size;
vector<int> DiskSectors;
void Read(...);
void Write(...);
};

class File
{
string Name;
shared_ptr<DiskArea> Area;

// адаптеры - расплата за правильный дизайн
void Read(...) { Area->Read(...); }
void Write(...) { Area->Write(...); }
};

09 января 2008

Function overloading vs template specialization

Перегрузка функций работает отлично со времен plain C. Пример:

Foo.h:
void Foo(bool);
void Foo(int);
void Foo(const char*);

Foo.cpp:
void Foo(bool) { ... }
void Foo(int) { ... }
void Foo(const char*) { ... }

Test.cpp:
#include "Foo.h"

void Test()
{
bool b(true);
int i(10);
const char *c("hello");

Foo(b);
Foo(i);
Foo(c);

Foo(*c); // хм... компилируется!

shared_ptr<int> p;
Foo(p); // тоже компилируется
}

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

Все было бы замечательно, если бы можно было написать void Foo(explicit bool);, но ведь в случае параметра функции explicit не прокатит...

Выход - вместо перегрузки функций использовать специализацию шаблонов. Правим Foo.h:
template <typename T> void Foo(T);
template <> void Foo(bool);
template <> void Foo(int);
template <> void Foo(const char*);

Кстати, у меня под Visual C++ 2005 не пришлось даже менять и/или перекомпилировать Foo.cpp - сингатуры функций в объектных файлах для template <> void Foo(xxx) и void Foo(xxx) видимо совпадают.

Теперь Foo(*c) и Foo(p) из приведенного выше примера не пройдут - линкер скажет, что не нашел определений нужных символов. Можно попросить ругаться не линкер, а компилятор, добавив немного кода в первую строчку Foo.h:
template <typename T> void Foo(T) { typename T::IncorrectParameterType; }


Из минусов такого подхода - все вызовы Foo() требуют теперь точного указания типа параметра:
class ConvertableToInt
{
public:
operator int() { return 0; }
};
Foo(ConvertableToInt()) // теперь так не получится :(
Foo(static_cast<int>(ConvertableToInt())) // только так;

Если такие минусы не устраивают - придется идти на более радикальные меры, например разделить реализации bool и не-bool на функции с разными именами. Интерфейс при этом останется тот же - Foo(x):

Новый Foo.h:
template <typename T> void Foo(T t) { FooImpl(t); }
void Foo(bool);
void FooImpl(int);
void FooImpl(const char*);

Новый Foo.cpp:
void Foo(bool) { ... }
void FooImpl(int) { ... }
void FooImpl(const char*) { ... }

23 декабря 2007

ADO::Recordset::Seek()

Изменить (или удалить) несколько записей в ADO::Recordset, источником которого служит база данных Microsoft Jet, можно довольно понятным подходом:
1. открывается нужная таблица в виде ADO::Recordset (с CursorLocation = adUseServer)
2. задается индекс, по которому будут отыскиваться записи
3. вызывается Seek() для быстрого переходу по ключу индекса
4. выполняется Update() (или Delete())
повторить п.п.3-4 нужное количество раз
5. закрыть открытый ADO::Recordset

Самое удивительное, что когда источник - MS SQL Server, такой подход не прокатит. Дело в том, что его провайдер SQL сервера не поддерживает метод Seek(). В инете миллион вопросов почему "оно" не работает. Ответ печальный: by design.

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

Но ведь индекс-то есть, и нужно использовать его. И раз Seek() не работает, выход один - открывать на каждую запись свой рекордсет вида "Select * From [Table] Where [KeyField]=KeyValue". Не забываем Recordset->CursorLocation = adUseServer.

12 ноября 2007

Integer division with rounding

Понадобилось мне целочисленное деление с округлением результата (точнее не целочисленное, а с фиксированной точкой - но суть-то, как известно, одна). "С округлением" - это когда 10/3=3, а 20/3=7.

С математикой у меня туго, поэтому написал это дело так:
__int64 RoundedDivision(__int64 Dividend, __int64 Divisor)
{
__int64 DoubleResult = (Dividend << 1) / Divisor;
return ((DoubleResult < 0) ? DoubleResult : (DoubleResult + 1)) >> 1;
}
Однако не покидает мысль что можно написать как-то попроще.

PS. Заодно немного потормозил над тем, что при отрицательном знаке DoubleResult не нужно вычитать единичку. Видимо совсем забыл как считать в дополнительном коде.

03 ноября 2007

Policy-based programming

Александреску в своей книге "Современное проектирование на С++" заразил меня тем, что он называет policy-based class design - разработка класса на основе его составляющих - "политик". Примером может являться умный указатель: при разработке обобщенного умного указателя можно не догадываться как именно конечному программисту потребуется от умного указателя:
а) управлять памятью (выделять память, удалять...),
б) реализовывать "владение" объектом (разделять одно значение, копировать...),
в) проверять значение указателя на правильность инициализации (через assert или исключения...),
г) ...

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

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

Пример:
template <class StoragePolicy, class OwnershipPolicy>
class StaticPolicyDemo
{
void Delete()
{
if (OwnershipPolicy::IsDeletable())
StoragePolicy::Delete(ptr);
}
...
};

class DynamicPolicyDemo
{
class IStoragePolicy
{
virtual void Delete(Object*) = 0;
...
};
class IOwnershipPolicy
{
virtual bool IsDeletable() = 0;
...
};

void Delete()
{
if (_OwnershipPolicy->IsDeletable())
_StoragePolicy->Delete(ptr);
}

DynamicPolicyDemo(PStoragePolicy StoragePolicy, POwnershipPolicy OwnershipPolicy) :
_StoragePolicy(StoragePolicy), _OwnershipPolicy(OwnershipPolicy)
{
}

void SetStoragePolicy(PStoragePolicy StoragePolicy)
{
_StoragePolicy = StoragePolicy;
}

void SetOwnershipPolicy(POwnershipPolicy OwnershipPolicy)
{
_OwnershipPolicy = OwnershipPolicy;
}

typedef std::auto_ptr<IStoragePolicy> PStoragePolicy;
typedef std::auto_ptr<IOwnershipPolicy> POwnershipPolicy;
PStoragePolicy _StoragePolicy;
POwnershipPolicy _OwnershipPolicy;
};
Такая конструкция очень полезна, когда нужно в run-time менять части поведения некоторого объекта. Например, графического контрола Grid это может быть: а) внешний вид графического элемента (skin), б) источник записей, в) фильтр записей, и др. Если часто используются типовые policy, то для их хранения логично использовать shared_ptr (вместо auto_ptr, приведенного в примере).

02 ноября 2007

Default window procedure

При обработке оконных сообщений типичная ситуация такова: обработал сообщение, вернул значение. А если не обработал - тогда уж оно пойдет в DefWindowProc() / DefMDIChildProc (). Оказывается, такой алгоритм не всегда верен.

Например, если не передать WM_SIZE в DefMDIChildProc() для MDI-child окна, то при максимизации этого окна пользователь не сможет восстановить его размер - у окна не будет кнопок minimize и restore (обычно они появляются с правой стороны полосы меню mdi parent-а).

Как мне кажется, общее правило для обработки событий лучше иметь таким: если обработал сообщение, но не нужно ничего возвращать (как в случае уведомительных сообщений типа WM_SIZE) - лучше передать сообщение дальше по цепочке обработчиков. В WTL это можно сделать так:
LRESULT WindowClass::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
do stuff;

return bHandled = 0; // сбрасываем флаг bHandled
}

16 октября 2007

Hash calculation

Как правильно заметил Not a kernel guy, лишний раз бросаться исключениями не стоит. Если знаешь как обработать ошибку - обработай ее сразу. Не знаешь - брось исключение, "на верху" разберутся.

В качестве примера - код получения хэша. В фокусе - вызов CryptGetHashParam:
#include <windows.h>
#include <Loki/ScopeGuard.h>
#include <boost/range/size.hpp>
#include <boost/static_assert.hpp>
#include "TestWinFn.h"

template <class TOutputContainer, typename TInputContainer>
void Hash(ALG_ID Algorithm, const TInputContainer& Input, TOutputContainer& Output)
{
BOOST_STATIC_ASSERT(sizeof(typename TOutputContainer::value_type) == sizeof(BYTE));

HCRYPTPROV hProv = 0;
TestWinFn(CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT));
LOKI_ON_BLOCK_EXIT(CryptReleaseContext, hProv, 0);

HCRYPTHASH hHash = 0;
TestWinFn(CryptCreateHash(hProv, Algorithm, 0, 0, &hHash));
LOKI_ON_BLOCK_EXIT(CryptDestroyHash, hHash);

TestWinFn(CryptHashData(hHash, reinterpret_cast<const BYTE*>(&Input[0]),
static_cast<DWORD>(boost::size(Input) * sizeof(Input[0])), 0));

DWORD HashSize = 0;
DWORD Error = CryptGetHashParam(hHash, HP_HASHVAL, NULL, &HashSize, 0) ? 0 : ::GetLastError();
if ((Error == ERROR_MORE_DATA) || (!Error && HashSize))
{
Output.resize(HashSize);
TestWinFn(CryptGetHashParam(hHash, HP_HASHVAL, &Output[0], &HashSize, 0));
}
else
throw WindowsError(Error);
}

15 октября 2007

Windows exceptions

Функции WinAPI сообщают об ошибках в C-стиле - через коды ошибок. Классика C++ - сообщать об ошибках через исключения. Достаточно обернуть вызов функции в специальный "адаптер", и брюки превращаются в элегантные шорты:
#include <comdef.h>

inline void TESTHR(HRESULT hr)
{
if (FAILED(hr))
_com_issue_error(hr);
};

...
TESTHR(::CoCreateGuid(&UniqueID));
Типичный адаптер для обработки ошибок COM. Не помешает иметь такой же адаптер для не-COM функций:
#include <windows.h>

inline void TestWinFn(BOOL WindowsFunctionResult)
{
if (!WindowsFunctionResult)
throw WindowsError(::GetLastError());
}

...
TestWinFn(::ConvertSidToStringSid(SID, &StringSID));
Единственное - не хватает того самого класса WindowsError.
#include <windows.h>
#include <exception>
#include <Loki/ScopeGuard.h>

class WindowsError : public std::exception
{
public:
WindowsError(DWORD ErrorCode = ::GetLastError())
std::exception(Message(ErrorCode).c_str()),
_ErrorCode(ErrorCode) {}

DWORD ErrorCode() const { return _ErrorCode; }

private:
static std::wstring Message(DWORD ErrorCode)
{
std::wstring Result;
LPVOID Buffer = NULL;

if (::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
ErrorCode,
0, // Default language
(LPTSTR) &Buffer,
0,
NULL))
{
LOKI_ON_BLOCK_EXIT(LocalFree, Buffer);
Result.assign((LPCWSTR)Buffer);
}

return Result;
}

private:
DWORD _ErrorCode;
};

PS. Кто не использует Loki - сюда.

01 октября 2007

WTL's cracked handlers

До чего же мне нравится идея "cracked handlers" в WTL, но вот реализация...

Идея cracked handlers такова. Обработчики оконных событий в WTL выглядят так:
class SomeWindowImpl
{
BEGIN_MSG_MAP(SomeWindowImpl)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_SIZE, OnSize)
END_MSG_MAP()

LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
};
Потом приходится копаться в MSDN и выковыривать из wParam и lParam нужную информацию. Идея cracked handlers проста - сделать все по-человечески:
class SomeWindowImpl
{
BEGIN_MSG_MAP(SomeWindowImpl)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
MSG_WM_SIZE(OnSize)
END_MSG_MAP()

LRESULT OnLButtonDown(UINT nFlags, CPoint Point);
LRESULT OnSize(UINT nType, CSize Size);
};
Все бы было нормально, если не видеть как реализованы все эти MSG_WM_xxx:
#define MSG_WM_LBUTTONDOWN(func) \
if (uMsg == WM_LBUTTONDOWN) \
{ \
SetMsgHandled(TRUE); \
func((UINT)wParam, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \
lResult = 0; \
if(IsMsgHandled()) \
return TRUE; \
}

#define MSG_WM_SIZE(func) \
if (uMsg == WM_SIZE) \
{ \
SetMsgHandled(TRUE); \
func((UINT)wParam, CSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); \
lResult = 0; \
if(IsMsgHandled()) \
return TRUE; \
}
Посмотришь на них - и начинаешь задумываться: что произойдет быстрее - появятся мониторы с 33 тыс.точек по горизонтали (или вертикали), или люди перестанут пользоваться программой, которую ты пишешь. Я, конечно, про GET_x_LPARAM - они работают до поры до времени, позже все же придется воспользоваться GetClientRect() и GetMessagePos(). Или не придется?... ;) Попробуй угадай.

А иногда там встречаются менее заметные вещи, типа использования unsigned вместо signed. Сразу и не догадаешься. Вобщем, в результате:
// #include <atlcrack.h>

23 сентября 2007

Trim

Всегда было интересно - почему реализации функции trim либо изменяют исходную строку, либо генерируют новую? Ведь результат функции - это подстрока, а ее можно задать парой итераторов. Тогда не потребуется никакого лишнего копирования:
template <typename TStringIterator>
std::pair<TStringIterator, TStringIterator> Trim(
TStringIterator Begin, TStringIterator End,
const std::locale& Locale = std::locale())
{
std::pair<TStringIterator, TStringIterator> Result(
::boost::algorithm::detail::trim_begin(Begin, End, boost::is_space(Locale)),
::boost::algorithm::detail::trim_end(Begin, End, boost::is_space(Locale)));
if (Result.first > Result.second)
Result.first = Result.second;
return Result;
}
PS. Вместо std::pair можно вернуть boost::iterator_range.

15 сентября 2007

Fixed point arithmetics

Почему-то в Boost-е до сих пор нет арифметики с фиксированной точкой. Была предложена подобная библиотека, но ее завернули, попросив автора немного доработать библиотеку. А автор на это забил. Поэтому приходится изобретать очередные велосипеды. Что-то типа такого:
template <unsigned Precision = 8, typename Integer = int>
class Fixed
{
public:
Fixed() {}
Fixed(Integer Value) : _Value(Value << Precision) {}
Fixed& operator +=(Fixed rhs) { _Value += rhs._Value; return *this; }
Fixed& operator +=(Integer rhs) { _Value += (rhs << Precision); return *this; }
Fixed operator /(Integer rhs) { return Fixed(_Value / rhs, RawValue); }
Integer Int() { return _Value >> Precision; }
Integer Round() { return (_Value + (1 << (Precision - 1))) >> Precision; }

private:
enum ERawValueFlag { RawValue };
Fixed(Integer Value, ERawValueFlag) : _Value(Value) {}

private:
typename Integer _Value;
};

03 сентября 2007

Reporting system на скорую руку

Задача: написать систему генерации и просмотра отчетов для некого бизнес-приложения
Исходные данные: данные из базы SQL

Решение:
1. Используем контрол, умеющий показывать некие XML-файлы. Например, внешний или встроенный в нашу программу веб-браузер.
2. Получаем с сервера SQL данные в виде XML.
3. С помощью XSLT преобразуюем данные в формате п.2 в формат для п.1 (например, XHTML).
4. Рендерим полученный в п.3 отчет в нашем контроле из п.1.
Готово.

Описание каждого отчета имеет вид:
а) команды для SQL-сервера
б) XSLT файл

31 августа 2007

Writing a calculator

Самая первая программа, написанная мною, была написана на Basic для Robotron 1715. Это был простейший калькулятор: вводите два операнда и оператор, и получаете результат.

Сейчас пишу вычисление формул, вспоминаю тот калькулятор ;) Задача сейчас такова: есть описание GUI в XML в виде
<control
  name="Button1"
  x="100"
  y="200"
  width="300"
  height="400"
/>


Необходима возможность понимать такие выражения:
<control
  name="Button1"
  x="Button2.x + 100"
  y="Form.height - 200"
  width="Form.width - (Button3.width*2 + 100)"
  height="400"
/>

И чтобы при resize окна (изменении пользователем размеров формы) пересчитались все зависимые от размеров формы координаты.

Как решалась задача:
1. Парсим каждую формулу на лексемы (операнды, операторы и скобки) - получаем выражение в инфиксной форме.
2. Переводим инфиксную форму в постфиксную.
3. Заменяем имена переменных (типа "Form.width" и "Button2.x") на указатели на них.
4. Одна формула может ссылаться на другую, та - на третью, четвертая - на первую и т.п. Поэтому топологической сортировкой выясняем порядок вычисления формул.
5. Вычисляем формулы по порядку, вычисленному в п.4. Формулы в постфиксной форме вычисляются очень просто и быстро.

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

PS. А мой первый калькулятор был гораздо проще - состоял всего из трех INPUT-ов и четырех IF-ов (на каждый поддерживаемый оператор).

28 августа 2007

Is it simple to write text editor for Windows?

Казалось бы - насколько просто написать небольшой текстовый редактор для Windows?

Вот очень интересный tutorial на эту тему: Design and Implementation of a Win32 Text Editor. Автор описывает как решать ту огромную кучу нюансов, с которыми приходится столкнуться: unicode, разные направления письма, большие тексты, переменная скорость прокрутки при выделении мышью и многое другое.

Жаль, автор не закончил сей труд. Но возможно он еще продолжит.

PS. Там же на сайте можно найти еще много интересного по Windows programming.

25 августа 2007

`this' is just an ordinary pointer

Как вы думаете, насколько надежна такая конструкция:
int Bar();
class Foo
{
int x;
public:
void y()
{
x = Bar();
}
};
На первый взгляд все отлично. До тех пор, пока не увидим всю картину:
Foo* foo;
int Bar()
{
if (...) delete foo;
return ...;
}
void Main
{
foo = new Foo();
foo->y();
}
Ситауция представлена здесь довольно утрированно, но суть понятна: мембер-функция Foo::y() вызывает другую функцию Bar(), которая удаляет объект. Тот самый объект, который Foo::y() знает как this. Далее Foo::y() пытается записать что-то в this->x, который уже не существует.

Реальна ли такая ситуация, или это ошибка архитектуры? Думаю, такая ситуация вполне может иметь место. Ведь мы давно свыклись с тем, что после vector::push_back() может привести в негодность все наши итераторы на этот вектор, и к другим подобным ситуациям. Нужно просто при вызове функции (в нашем случае Bar()) знать, что this может стать недействительным.

Такая ситуация может возникнуть, например, если элементы GUI (controls) реализованы как объекты (привет WTL):
void Button::OnClick()
{
Dialog.Close();
// this больше недействителен!
}
Вобщем, this - это такой же обычный указатель, и логично сделать его умным указателем. Пусть сам о себе заботится.
class Foo : public boost::enable_shared_from_this<Foo>
{
int x;
public:
void y()
{
boost::weak_ptr<Foo> This(shared_from_this());
int bar = Bar();
if (!This->expired()) x = bar;
}
};

boost::shared_ptr<Foo> foo;
int Bar()
{
if (...) foo.reset();
return ...;
}

12 августа 2007

Smart pointer with copy semantic

Умные указатели - вещь полезная не только для уменьшения кода, но и отсутствием головной боли с удалением объектов. Например, следующий класс легко положить в контейнер, для него можно не переопределять копирующий конструктор и оператор присваивания:
class Foo
{
std::vector<Bar> A;
boost::shared_ptr<Bar> B;
};
Члены A и B сами позаботятся о своем копировании: A сделает deep copy своего содержимого в новый объект, B будет вести подсчет ссылок.

Однако, почему-то в популярных (STL и boost) библиотеках нет умного указателя, аналогичного shared_ptr, но не с подсчетом ссылок, а с глубоким копированием. То есть чтобы он указывал на один (или ноль, если SmartPointer=NULL) объект, а не как vector - на массив. Подходящий указатель есть в Loki, благо там все разбито по стратегиям и можно задать любую стратегию копирования (подсчет ссылок, глубокое копирование, ...). Однако лишний раз иметь зависимость от Loki не всем удобно. Писать свой велосипед - еще менее удобнее. Компромисный вариант: использовать std::vector, кладя в него не больше одного элемента. Звучит смешно, но работает.

04 августа 2007

mutable

Интересно, почему не все понимают смысл и полезность ключевого слова mutable. Истинная его ценность состоит в том, чтобы дать возможность в const member-функциях производить такие изменения в объекте, которые не видны снаружи. Все, что видно "снаружи" пользователям объекта - это то, что можно получить через публичный интерфейс. Это нужно для кэширования и ленивых вычислений.

Например, мы пишем адаптер, позволяющий приводить строки типа const char* и std::string к единому интерфейсу вида Data, Size. Для const char* размер можно расчитать через strlen() сразу (не важно, потребуется нам результат вычислений или нет), но можно сделать это только если понадобится:
class StringAdapter
{
public:
explicit StringAdapter(const std::string& String) :
_Data(String.data()), _Size(String.size()), _SizeCached(true) {}

explicit StringAdapter(const char* String) :
_Data(String), _SizeCached(false) {}

const char* Data() const { return _Data; }

size_t Size() const
{
if (!_SizeCached) { _Size = strlen(_Data); _SizeCached = true; }
return _Size;
}

private:
const char* _Data;
mutable size_t _Size;
mutable bool _SizeCached;
};
Без mutable мы бы не смогли внутри Size(), объявленной как const, модифицировать наш кэш.

26 июля 2007

Screen DPI in Vista

В Windows есть замечательная возможность - можно вручную указать DPI экрана. Приложения автоматически будут учитывать этот параметр, если используют "правильный" mapping mode, или учитывают этот параметр вручную через вызов GetDeviceCaps(ScreenDC, LOGPIXELSX / LOGPIXELSY) для режима MM_TEXT.

Видимо далеко не все разработчики учитывали DPI в режиме MM_TEXT (а этот режим используется по умолчанию), поэтому в Висте появился новый режим работы при увеличенном DPI (старый назвали XP style DPI scaling):



В этом режиме (когда указанная галочка снята) Виста полагает, что если приложение явно не сообщило, что оно умеет работать с разными dpi, то значит оно масштабировать нифига не умеет. И Виста будет делать это масштабирование насильно.

В моем текущем проекте весь GUI самописный и в нем с самого начала заложена поддержка различных dpi, т.к. значительная часть пользователей сидит на "крупных шрифтах" (они же 120dpi). Поэтому мне стало очень интересно, как будет выглядеть наше приложение в этом новом режиме. Так как никаких вызовов SetProcessDPIAware() и пометок в манифесте по поводу dpiAware нет, то Виста должна принять нас за лохов и применить добавочное масштабирование, что в сумме с заложенной в нашем GUI логикой в итоге должно дать двойное масштабирование. Однако этого не произошло - все выглядит как в XP. Довольно странно...

Полезная статья на эту тему: DPI-aware applications in Windows Vista. В частности, там указывается, что при собственноручной отрисовке иконок можно выбрать наиболее подходящий размер иконки из доступных:
// images available in sizes 16x16, 20x20, and 24x24
int nToolbarImageSize = (16*fScale+0.5f) >= 24 ? 24 : ((16*fScale+0.5f) >= 20 ? 20 : 16);
Идея очень правильная, так как при точном масштабировании иконки получаются довольно кривыми, поэтому достаточно выбрать наиболее близкую по размеру и отрисовать ее 1:1. Однако, не стоит это делать приведенным выше методом, гораздо разумнее положить все доступные размеры в контейнер и воспользоваться алгоритмом std::lower_bound.

Самая гениальная идея в этой статье - это методика использования ограниченного набора иконок для отрисовки в заголовке окна. Вся проблема в том, что иконку в заголовке окна рисует операционная система, и нельзя как в предыдущем случае подсунуть ближайшую по размеру иконку из набора доступных. Windows все равно ее не отрисует 1:1, а отмасштабирует к размеру точно согласно текущему DPI. Гениальная идея состоит в том, чтобы взять наиболее подходящую иконку и добавить ей прозрачные края, догнав тем самым ее размер под тот, который потребуется Windows. И овцы целы (иконка не исказится), и волки сыты (Windows получает иконку нужного размера).

24 июля 2007

Boost.ForEach and rvalue [2]

По поводу использования rvalue-(proxy)контейнеров в Boost.ForEach: Eric Niebler, создатель этой библиотеки, пояснил мне, что намерено выбрал использование const_iterator-ов для rvalue объектов, чтобы предотвратить ненамеренное изменение контейнеров, возвращенных как rvalue. Что касается proxy-контейнеров, таких как boost::iterator_range, то Эрик предлагает приравнять в них константные итераторы к обычным, так как изменение самого прокси-контейнера не происходит. Цитирую:

The interface I chose preserves object lifetimes and prevents inadvertent mutation of temporary objects. Weakening its guarantees is a bad idea.

If you want the proxy to offer either a const or mutable interface *and* you want our proxy to work with BOOST_FOREACH, you should base it on the const-ness or mutability of the object being proxied, not the constness of the proxy itself. Consider:
template<class Range>
struct proxy {
typedef typename range_result_iterator<Range>::type iterator;
typedef typename range_result_iterator<Range>::type const_iterator;
iterator begin() const { return boost::begin(rng_); }
iterator end() const { return boost::end(rng_); }
// etc ...
Range &rng_;
};
Now, you can have const and mutable proxied objects like:
// ok, a mutable proxy
proxy< std::vector<int> > p1;

// ok, still a mutable proxy
proxy< std::vector<int> > const p2;

// ok, a const proxy
proxy< std::vector<int> const > p3;

// still a const proxy
proxy< std::vector<int> const > const p4;

23 июля 2007

Boost.ForEach and rvalue

Когда я начал использовать Boost.ForEach, первая мысль, которая пришла мне в голову, была: как делается выбор между iterator и const_iterator? Сначала я не стал тратить время на выяснение этого вопроса, но потом все-таки пришлось это сделать - один фрагмент моего кода не хотел компилироваться при использовании BOOST_FOREACH. Нижеприведенный код демонстрирует проблему:
typedef vector<string> vec;
vec get_vector();

void test()
{
// отлично компилируется
get_vector().push_back("oh god, it's writable!");

// получаем невозможность сконвертировать const string в string&
BOOST_FOREACH(string& s, get_vector())
{
...
}
}
В моем случае был, конечно не vector, а прокси-контейнер, но суть понятна: при использовании rvalue-контейнера выбирается const_iterator и получаем невозможность модифицировать значения в контейнере.

Но ведь в том же бусте есть прокси-контейнеры (iterator_range и ко), неужели не подумали о них? Очень не верится.

И я не ошибался свято веря в создателей foreach - с iterator_range приведенная конструкция успешно работает! Оказывается, для определения прокси-контейнеров предусмотрен специальный хак (по-другому не назовешь) - boost_foreach_is_lightweight_proxy.

Однако ни использование ::boost_foreach_is_lightweight_proxy, ни boost::foreach::is_lightweight_proxy почему-то не помогло мне скомпилировать приведенный выше код:
inline boost::mpl::true_ *
boost_foreach_is_lightweight_proxy(vec *&, boost::foreach::tag) { return 0; }
Вот теперь думаю - то ли я тупой, то ли сани не едут.

19 июля 2007

Exception Handling Cost

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

Наткнулся в Google.Video на презентацию Exception Handling Cost. Информация из первых уст: автор занимается обработчиками исключений в команде компилятора Visual C++.

PS. файл с презентацией

17 июля 2007

Boost.Iterator

При реализации шаблонных алгоритмов, наткнулся на необходимость приводить указатели к ссылкам. В случае с контейнерами и интервалами все довольно просто - boost::indirect_iterator помогает конвертировать итераторы по указателям в итераторы по ссылкам.

Как быть с обычными указателями, в бусте так и не нашел. Тот же boost::ref не инициируется из указателя, только из ссылки. Можно написать что-то типа:
template <typename T>
inline T& GetReference(T& Reference)
{
return Reference;
}

template <typename T>
inline T& GetReference(T* Pointer)
{
return *Pointer;
}
...но что-то мне кажется что в бусте есть что-то подобное, вопрос - где?

Кстати, в том же бусте обнаружил filter_iterator, который недавно собственноручно изобретал в качестве велосипеда :-E. А стоило лишь заглянуть в boost. Вобщем, как в Южном парке - "Это уже было в Симпсонах!"

02 июля 2007

WTL 8.0

Зарелизился WTL 8.0. Никаких особых killer features не появилось. Теперь WTL может работать с ATL 3.0, что актуально когда Platform SDK скачано с сайта Microsoft, а не получено в комплекте с Visual Studio. Такой момент имеет быть при использовании Visual Studio 2005 Express.

19 июня 2007

Boost 1.34

Вышла новая версия Boost. Не сказал бы "о, какие там новые вкусности!", а скорее "почему этого до сих пор не было???". Это касается таких вещей как optional, unicode пути для filesystem, for each, typeof, вставка auto_ptr в ptr_containers.

10 июня 2007

Case-insensitive string comparision

Сортировать строки по алфавиту, не при этому учитывая регистр букв, довольно просто:
std::sort(container.begin(), container.end(), std::locale());

Однако использовать подобный предикат для регистро-независимого сравнения строк не получится: std::collate::compare() возвращает ноль для полностью одинаковых строк (что означает их равенство), но вернет не ноль для одинаковых строк, различающихся регистром (например "hello" и "Hello").

Простой тест, чтобы показать несостоятельность std::collate::compare() (или его обертки в виде std::locale::operator()) для использования в ассоциативных контейнерах:
std::set<std::string, std::locale> s;
s.insert("hello");
s.insert("Hello");
std::cout << s.size(); // выведет 2
Предикат std::locale не считает строки "hello" и "Hello" эквивалентными.

Брутальный подход - перевести строки в верхний регистр и уже тогда сравнивать:
template <class CharType>
void ToUpper(std::basic_string<CharType>& String, const std::locale& Locale)
{
if (!String.empty())
std::use_facet< std::ctype<CharType> >(Locale).toupper(&*String.begin(), &*String.begin() + String.size());
}

struct LexicographicalLess
{
LexicographicalLess(const std::locale& Loc = std::locale()) : Locale(Loc) {}
std::locale Locale;

template <typename CharType>
bool operator()(std::basic_string<CharType> lhs, std::basic_string<CharType> rhs) const
{
ToUpper(lhs, Locale);
ToUpper(rhs, Locale);
return Locale(lhs, rhs);
}
};
Такой предикат уже пройдет наш мини-тест.

Однако скорость не впечатлит. В процессе сравнения делаются копии строк, что довольно долго. Особенно учитывая то, что std::basic_string хранит более-менее большие строки в динамической памяти.

Кстати, если предикат нужен только для сравнения строк, а не для сортировки (для сортировки у нас есть std::locale в качестве предиката), строку return Locale(lhs, rhs); можно заменить на более быструю return lhs < rhs;, так как для сравнения нам не важно положение буквы в алфавите, сойдет и ее положение в кодовой таблице.

Увеличить производительность LexicographicalLess можно избавившись от копирования строк:
struct LexicographicalLess
{
LexicographicalLess(const std::locale& Loc = std::locale()) : Locale(Loc) {}
std::locale Locale;

template <typename CharType>
bool operator()(CharType lhs, CharType rhs) const
{
return std::toupper(lhs, Locale) < std::toupper(rhs, Locale);
}

template <typename CharType>
bool operator()(const std::basic_string<CharType>& lhs, const std::basic_string<CharType>& rhs) const
{
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), LexicographicalLess(Locale));
}
};

Еще немного дополнительной производительности мне удалось выжать написав свою версию lexicographical_compare.

28 мая 2007

Auto-sorted vector

Помнится еще Александреску в одной из своих книг упоминал как может сортированный vector или deque по скорости поиска элементов превосходить std::set и std::multiset. В последних контейнерах идет большой оверхед по размеру потребляемой памяти, из-за чего процессору приходится больше данных читать из памяти. Недостаток сортированных vector/deque при вставке - слишком часто придется двигать элементы, если вставлять сразу в нужное место. Однако часто вставка идет большой порцией элементов, что позволяет сначала накидать новые элементы как попало (через push_back), а потом отсортировать контейнер.

Что удивительно, ни в boost, ни в Loki я подобного сортированного вектора в виде отдельного класса (шаблона классов) не нашел. В Loki есть подобная штука - AssocVector, но реализует она функциональность не set/multiset, а map. Тоже, кстати, весьма полезная вещь.

Google навел меня на класс с нужной функциональностью на сайте codeproject. Однако там он какой-то сыроватый, VC++-only, да и без набора юнит-тестов, так что использовать его в реальном проекте я бы не стал. Те, кому наплевать на сырость могут вполне его использовать, а к более продвинутым людям просьба сделать подобную вещь нормально и пропихнуть ее таки в boost, т.к. предыдущие "пропихиватели" похоже не справились (см. здесь и здесь).

Остальные же могут использовать std::vector и std::deque, просто сортируя их и получая прирост производительности (по сравнению с std::set/multiset). Ключевых функций для написания будет две: insert и find. Их можно легко реализовать через родные сердцу алгоритмы бинарного поиска - std::lower_bound() и std::upper_bound().

Ссылка по теме: Why you shouldn't use set (and what you should use instead)

26 мая 2007

XML Data Binding in C++

Статья An Introduction to XML Data Binding in C++ на The C++ Source напоминает, что DOM И SAX - уже прошлый век, и давно уже пора мапить XML-данные на C++-классы.

Суть такова: пишите XML Schema для своих XML, некая утилита (binding compiler) автоматически генерирует по нему C++-классы, с которыми намного приятнее общаться, нежели, например с DOM:

XML:
<person>
<name>John Doe</name>
<gender>male</gender>
<age>32</age>
</person>

C++:
ifstream ifs ("person.xml");
auto_ptr<person_t> p = person (ifs);

if (p->age () > 30)
cerr << p->name () << endl;

24 мая 2007

Interview with A. Stepanov

Интересное интервью с Александром Степановым, главным идеологом и создателем STL. Особенно примечательно как он не любит ООП в общем и Java в частности.

19 мая 2007

Loki:ScopeGuard

Александреску - умный парень, но наверно скорее теоретик, чем практик. Иначе как объяснить такое:

В Loki можно найти полезную вещь - scope guard. Поясню зачем она нужна. Все знакомы с RAII и с тем, что очень удобно делать работу по управлению ресурсами в деструкторе - это безопасно с точки зрения исключений, так как при откате стека по исключению вызываются деструкторы всех уничтожаемых объектов. Например:
class File
{
public:
File(const char* FileName) { OpenFile(FileName); }
~File() { CloseFile(); }
};

{
File f("test.txt");
Foo();
}
Файл закроется даже в случае если Foo бросит исключение.

Однако создавать такие RAII-классы для каждого случая - довольно муторная работа. Представьте:
{
class Guard
{
public:
Guard(Module& M, Window& W) : m(M), w(W) {}
~Guard() { w.Close(); m.Terminate(); }
private:
Window& w;
Module& m;
}

Guard g(module, window);
Foo();
}
Гораздо проще было бы жить, если бы можно было записать все это намного короче:
{
AutoGuard(window.Close());
AutoGuard(module.Terminate());
Foo();
}
Так вот предложенная Александреску конструкция такое счастье и привносит в нашу жизнь, выглядит это так:
{
LOKI_ON_BLOCK_EXIT_OBJ(window, Windows::Close);
LOKI_ON_BLOCK_EXIT_OBJ(module, Module::Terminate);
Foo();
Все бы замечательно, но это не компилируется без using namespace Loki, так как макрос LOKI_ON_BLOCK_EXIT_OBJ выглядит так:
#define LOKI_ON_BLOCK_EXIT_OBJ ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = MakeObjGuard
А ведь дядька Александреску мог бы и пожалеть людей и написать так:
#define LOKI_ON_BLOCK_EXIT_OBJ ::Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = ::Loki::MakeObjGuard

А ведь не написал. Видимо не использовал это в реальных проектах. Или у него там сплошные using namespace xxx стоят?

PS. Оказывается уже исправили.

15 мая 2007

WinMain

Объявление WinMain имеет вид:
int WINAPI WinMain(      
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
Не видите ничего странного?

lpCmdLine имеет тип LPSTR, а по хорошему нужно бы LPTSTR (или даже LPCTSTR, но не суть). Кстати, GetCommandLine() возвращает LPTSTR.

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

12 мая 2007

Windows Sysinternals

Много полезных Windows-разработчику и администратору утилит можно найти на сайте Sysinternals, в частности RegMon и Process Explorer.

10 мая 2007

When edit control sends WM_CTLCOLORSTATIC

Edit control при отрисовке своего фона шлет свому "родителю" сообщение WM_CTLCOLOREDIT, в качестве результата получает HBRUSH, которым и рисует свой фон. Static контролы шлют WM_CTLCOLORSTATIC. Однако, бывают ситуации, когда и Edit запрашивает фон через WM_CTLCOLORSTATIC. Это происходит в двух случаях:
1. когда контрол заблокирован - EnableWindow(Edit, FALSE)
2. когда контрол в режиме read-only - SendMessage(Edit, EM_SETREADONLY, TRUE)

Чтобы read-only edit выглядел не как заблокированный а как обычный контрол, родителю нужно перехватить WM_CTLCOLORSTATIC и подменить его на WM_CTLCOLOREDIT:
LRESULT SomeWindow::OnCtlColorStatic(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// paint read-only edits as usual edits
return DefWindowProc(IsWindowEnabled((HWND)lParam) ? WM_CTLCOLOREDIT : WM_CTLCOLORSTATIC, wParam, lParam);
}
Можно также использовать технику message reflection, перенеся выбор фона с родителя на сам edit control.

05 апреля 2007

How to reuse try-catch blocks

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

Цель такова: есть некий код, который может генерировать исключения. Есть набор catch блоков, каждый из них знает как обработать свой тип исключений, причем исключения могут не являться классами одной иерархии. Совершенно разные исключения. Как повторно использовать этот набор catch-блоков? Вот небольшой трюк: обернем наш код в функтор boost::function и скормим функции, которая будет знать как обрабатывать исключения, например такой:
void HandleExceptions(const boost::function<void()>& Job)
{
try
{
Job();
}
catch (std::exception& Exception)
{
ReportException(Exception.what());
}
catch (const char* Exception)
{
ReportException(Exception);
}
catch (_com_error& Exception)
{
ReportException(Exception.Description());
}
catch (SomeXMLException&)
{
ReportException("Error parsing XML file.");
}
catch (...)
{
ReportException("Unknown exception");
}
}

Использование тривиально: оборачиваем нужный код в функцию или метод и передаем в качестве параметра в HandleExceptions:
HandleExceptions(boost::bind(Function, Parameter1, Parameter2));


Я использовал такой подход при выводе сообщений пользователю о необработанных исключениях. В моем приложении есть слой бизнес-логики, который постоянно меняется , пишется очень быстро и можно сказать в полевых условиях. Такая обстановка не способствует детальному тестированию и где-то легко может вылететь необработанное исключение, которое и ловится на границе бизнес-логики и ядра системы (бизнес-логика вызывается из ядра). Поймав исключение сообщаем пользователю об ошибке и продолжаем работать (ядро продолжает работать). Однако подобный глобальный обработчик для ядра тоже не помешает, мало ли что случится - сообщим пользователю ("программа выполнила некорректную операцию ;)) и завершим программу. Как раз тот try-catch блок по преобразованию исключений в сообщения об ошибках.

В примере использован вызов функции ReportException(), но можно вставить туда генерацию какого-нибудь исключения определенного типа, например преобразовать все пойманные исключения к std::exception. Или даже вернуть сообщение об ошибке из HandleException() по значению через return.

24 марта 2007

Declarative programming

В продолжение темы предыдущего поста про философию программирования. Всем, кто "застрял" в императивном программировании, предлагаю расширить кругозор программированием декларативным.

В частности, можно почитать книгу по Хаскелю и посмотреть видео-лекции по Лиспу.

22 марта 2007

Языковой барьер

Очень правильная заметка - Языковой барьер.

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

Больше внимания нужно уделять именно "духу" языка. Не стоит писать на C++ как на "расширенном C". Следует проникнуться RAII, шаблонами, умными указателями и патернами.

Всем, кто не читал, рекомендую книги Александреску, Саттера и Мэйерса. Особенно Современное проектирование на С++.

06 марта 2007

clone()

Иногда есть "тяжеловесные" классы, для которых нежелательно допустить неявное копирование. Обычно это делается так:
class Foo
{
private:
Foo(const Foo&);
Foo& operator=(const Foo&);
};
Однако, возникает вопрос: как все-таки явно скопировать объекты такого типа?

Хорошее решение этого вопроса: использование функции clone(), как это сделано в Boost Pointer Container Library:
ptr_vector<T> v1, v2(v1.clone());
v2 = v1.clone();
Clone() создает свой клон на куче (да хоть через тот же приватный конструктор копирования) и возвращает auto_ptr на него, а соотвутствующий конструктор и оператор присваивания делают свое дело через swap():
class Foo
{
public:
void swap(Foo&);
auto_ptr<Foo> clone() const;

Foo(auto_ptr<Foo> clone)
{
swap(*clone);
}

Foo& operator=(auto_ptr<Foo> clone)
{
swap(*clone);
return *this;
}
};

04 марта 2007

Bicycles and Weak pointers

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

У меня в проекте есть такой веселый код: некие объекты запоминают указатели на другие объекты, и прежде чем этими указателями воспользоваться - проверяют их на валидность. Как проверяют? Некий глобальный объект-менеждер ведет список указателей на живые объекты (при их уничтожении объекты его оповещают, и он удаляет соответствующий указатель из списка). И если у него спросить - есть ли такой-то указатель у него в списке - то можно сделать вывод, существует ли сейчас объект по этому указателю или нет. Так вот валидность указателей и проверяется.

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

А все могло бы быть и без этой головной боли. Если бы в качестве указателя на объект мы запомнили не простой указатель, а велосипед под названием weak pointer, и при обращении к объекту просто лочили его.

27 февраля 2007

GetOpenFileName issues

MSDN пишет:
OFN_NOCHANGEDIR
Restores the current directory to its original value if the user changed the directory while searching for files.
Windows NT 4.0/2000/XP: This flag is ineffective for GetOpenFileName.

Однако под XP этот флаг великолепно работает. Интересно, что они имели ввиду под "ineffective"?

24 февраля 2007

IDispatch Wrapper

Работать с COM-объектами из pure C довольно неудобно. Тут одназначно нужно использовать разные C++ wrapper-ы и helper-ы. Некоторые из них доступны "из коробки": _com_ptr_t, _variant_t, директива #import. Но с IDispatch все же не особо удобно работать, как например в Visual Basic.

Немного погуглив обнаружил отличный wrapper для IDispatch by Mike Morearty. Позволяет намного упростить жизнь:
CDispatchPtr htmldoc = ...;
_bstr_t html = htmldoc.Get("body").Get("innerHTML");
htmldoc.Put("title", "New Title");
htmldoc.Get("body").Get("firstChild").Invoke("insertAdjacentText", "afterBegin", "hello world");

Надо будет еще в дополнение к нему написать wrapper для COM-коллекций в STL-совместимые контейнеры.

PS. Хотя в этом IDispatch wrapper-е явно не сказано про лицензию, но по почте Майк ответил "you're free to use the code, and to modify it."

19 февраля 2007

The sence of Boost.Ref

Неприятная особенность boost::bind заключается в том, что все параметры передаются по значению. Тем самым происходит "обрезание" производного объекта до его базового класса в случае, если передается ссылка на базовый класс.

Выход - использование boost::reference_wrapper, который инкапсулирует в себя ссылку и уже далее передается по значению:
class base
{
public:
virtual std::string whoami() const { return "base"; }
virtual ~base() {};
};

class derived : public base
{
public:
virtual std::string whoami() const { return "derived"; }
};

void whoisthis(const base& obj)
{
std::cout << obj.whoami() << std::endl;
}

void FunctorsTest3()
{
derived d;
base& b = d;

whoisthis(d); // works ok
boost::bind(whoisthis, b)(); // cut derived to base
boost::bind(whoisthis, boost::ref(b))(); // works ok
}

15 февраля 2007

Loki::Functor vs boost::function

Исследовал пути решения простенькой задачи.

Задача

1. Есть иерархия классов
class Base {};
class Derived : public Base {};
...

2. Есть функторы, принимающие ссылки на Base, Derived, etc...

3. Есть динамический массив функторов из п.2

Задача: обработать объект из иерархии Base функторами из массива п.3.

Исходные данные

Функторы, входящие в массив:
void Handler1(Base&);
void Handler2(Derived&);

Вспомогательный "конвертер" функторов для приведения к их одному типу:
template <typename T>
void HandlerConversion(void (*Handler)(T&), Base& c)
{
if (T* t = dynamic_cast<T*>(&c))
Handler(*t);
}

Loki

Вспомнив, что функторы есть в Loki, попробовал начать с этой библиотеки:
typedef Loki::Functor<void, LOKI_TYPELIST_1(Control&)> ControlFunctor;
typedef std::vector<ControlFunctor> FunctorVector;

И тут началось...

Такой вод код работает:
template <class T>
void RegisterHandler(FunctorVector& v, void (*Handler)(T&))
{
Loki::Functor<void, LOKI_TYPELIST_2(void(*)(T&), Control&)> cf(HandlerConversion<T>);
v.push_back(Loki::BindFirst(cf, Handler));
}

А такой вот вылетает с ошибкой из-за проблем с auto_ptr внутри Loki::Functor:
template <class T>
void RegisterHandler(FunctorVector& v, void (*Handler)(T&))
{
v.push_back(Loki::BindFirst(Loki::Functor<void, LOKI_TYPELIST_2(void(*)(T&), Control&)>(HandlerConversion<T>), Handler));
}

Дальше - больше. Если объявить переменную FunctorVector v; внутри тестовой main(), то все работает отлично, а если как глобальную переменную - вылетает при выходе из main(). Помогает v.clear(); в конце main(). Жесть какая-то. Подобную веселую реализацию Loki::Functor не решился использовать в рабочем проекте.

Boost

Boost тоже имеет реализацию функторов - boost::function:
typedef boost::function<void (Base&)> ControlFunctor;
typedef std::vector<ControlFunctor> FunctorVector;


Здесь все гораздо проще:
template <class T>
void RegisterHandler(FunctorVector& v, void (*Handler)(T&))
{
v.push_back(boost::bind(HandlerConversion<T>, Handler, _1));
}

И главное - работает как часы (в отличие от Loki).

17 января 2007

Painting of disabled icons

Не найдя стандартных путей рисования иконки в "disabled" состоянии, пришлось изобретать собственный велосипед. Windows предлагает сделать подобное либо имея static control с иконкой и задав ему EnableWindow(FALSE), либо имея ImageList и рисуя уже из него. Мне же нужен был простой способ отрисовать HICON на HDC не плодя при этом control-ов и ImageList-ов.

Велосипед довольно успешно был собран из подручных компонентов - функций WinAPI и небольшой приправы из цикла по перемалыванию байтов:
void DrawDisabledIcon(HDC DC, CRect& Rect, WTL::CIcon& Icon)
{
WTL::CDC MemDC(CreateCompatibleDC(DC));

BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = Rect.Width();
bmi.bmiHeader.biHeight = Rect.Height();
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = bmi.bmiHeader.biWidth * bmi.bmiHeader.biHeight * 4;

VOID *pvBits;
WTL::CBitmap Bitmap(::CreateDIBSection(MemDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0));
WTL::CBitmapHandle PrevBitmap(MemDC.SelectBitmap(Bitmap));

Icon.DrawIconEx(MemDC, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biWidth);

// convert to grayscale
for (unsigned char *p = (unsigned char*)pvBits, *end = p + bmi.bmiHeader.biSizeImage; p < end; p += 4)
{
// Gray = 0.3*R + 0.59*G + 0.11*B
p[0] = p[1] = p[2] =
(
static_cast<unsigned int>(p[2]) * 77 +
static_cast<unsigned int>(p[1]) * 151 +
static_cast<unsigned int>(p[0]) * 28
) >> 8;
}

BLENDFUNCTION BlendFunction;
BlendFunction.BlendOp = AC_SRC_OVER;
BlendFunction.BlendFlags = 0;
BlendFunction.SourceConstantAlpha = 0x60; // half transparent
BlendFunction.AlphaFormat = AC_SRC_ALPHA; // use bitmap alpha

AlphaBlend(DC, Rect.left, Rect.top, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight,
MemDC, 0, 0, bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight, BlendFunction);

MemDC.SelectBitmap(PrevBitmap);
}
Методика проста - отрисовываем иконку во временный буфер, переводим ее там в gray scale и выводим через AlphaBlend(), так как сама иконка содержит в себе alpha-канал. При AlphaBlend-е можно еще подправить ее прозрачность, задав SourceConstantAlpha.

08 января 2007

Case insensitive search performance

Сегодня оптимизировал одно из узких мест в проекте: case insensetive поиск подстроки в строке - то есть не различая регистр.

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

Провел сравнение скорости разных способов проверки вхождения подстроки в строку.
За единицу скорости принял std::wstring::find() - она хотя не делает того, что нужно (не регистро-независимая), но в качестве эталона скорости - самое оно. Тестировал в VC++ 7.1 release mode с включенной оптимизацией по скорости.

Аналог std::wstring::find() из буста - boost::find_first() - оказался в 2,5 раза медленнее эталона.

Та функция из буста, которая делает то, что мне нужно - boost::ifind_first() - в 70 раз медленнее эталона. В коде проекта как раз она и использовалась - отсюда и дикие тормоза.

Первая моя вариация была такой: делаем копии строк, затем применяем к ним boost::to_upper(), затем - std::wstring::find(). Результат - в 51 раз медленнее эталона. Но это уже заметно быстрее boost::ifind_first().

Следующая версия - создание глобальной таблицы wchar_t ToUpper[0x10000], содержащая для каждого символа его upper case варианта. Используя эту таблицу внутри предиката для функции boost::first_finder(), получил версию, всего в 2 раза медленнее эталона!

При нежелании тратить 128Kb на подобную таблицу, ее можно сократить до 2Kb, просчитывая только ее начало и используя подобным образом: (c <= 0x451) ? ToUpper[c] : std::toupper(c, loc). В таком случае скорость получилась в 5 раз медленнее эталона.

Выбрана была последняя версия с буфером 2Kb, куда влезает вся латиница и кириллица. Дальнейшая оптимизация свелась к тому, что искомая подстрока переводилась в upper case всего один раз, а ToUpper[] применялась только к строкам в которых шел поиск. Этот финт, правда, почти не сказался на скорости.

31 декабря 2006

Properties in C++

Замечательная штука есть в современных языках программирования - properties. Берешь и пишешь: Object.Visible = True и объект отображается на экране. Хочешь проверить, отображен ли объект - пишешь If Object.Visible Then...

Все так просто! Visible - это свойство объекта "быть показанным на экране". Мы можем легко читать и изменять это свойство зная его имя.

В C++ для этого необходимо сделать accessor и mutator, то есть две функции: одна позволяющая получить значение, вторая - изменяющая значение (со всеми последствиями для объекта). Если accessor можно назвать именем свойства - bool Visible() - то как быть с mutator-ом? Назвать его Visible(bool)? Не красиво. SetVisible(bool)? Не очень понятно. Show(bool)? А как запомнить что это mutator к свойству Visible?

Более красивый вариант - прикрутить properties к C++. Загоревшись этой идеей набросал шаблон property:
template <typename T, class C>
class property
{
public:
typedef T (C::*accessor_t)() const;
typedef void (C::*mutator_t)(const T&);

property(C* object, accessor_t accessor, mutator_t mutator) :
_object(object), _accessor(accessor), _mutator(mutator) {}

operator T() const { return ((*_object).*(_accessor))(); }
property& operator=(const T& value) { ((*_object).*(_mutator))(value); return *this; }

private:
C* _object;
accessor_t _accessor;
mutator_t _mutator;
};

class range
{
public:
range(int begin, int end) : _begin(begin), _end(end) {}

int begin() const { return _begin; }
int end() const { return _end; }

public:
property<int, range> size() { return property<int, range>(this, size, setsize); }

int size() const { return _end - _begin; }
void setsize(const int& size) { _end = _begin + size; }

private:
int _begin, _end;
};

void TestProperties()
{
range r(1, 5);
cout << "range is [" << r.begin() << "," << r.end() << ") size=" << r.size() << endl;

r.size() = 2;
cout << "range is [" << r.begin() << "," << r.end() << ") size=" << r.size() << endl;
}

29 декабря 2006

Dynamicaly created SQL statements

Переписываю приложение с VBA на C++. В приложении во всю используются динамически создаваемые SQL-запросы:

Было:
Условие = ""
If VarType(Me![Начальная дата]) > vbNull Then ДобавитьУсловие Условие, "[Дата] >= " & ДатаДляSQL(Me![Начальная дата])
If VarType(Me![Конечная дата]) > vbNull Then ДобавитьУсловие Условие, "[Дата] <= " & ДатаДляSQL(Me![Конечная дата])
If VarType(Me![Тип]) > vbNull Then ДобавитьУсловие Условие, "[Тип] = " & Me![Тип]
If VarType(Me![Эмитент]) > vbNull Then ДобавитьУсловие Условие, "[Эмитент] = " & Me![Эмитент]
If VarType(Me![Структура]) > vbNull Then ДобавитьУсловие Условие, "[Структура] = " & Me![Структура]
Me![Список].RowSource = "SELECT * FROM [События - список]" & OptionalWhereStatement(Условие) & " ORDER BY [Дата], [Название общества]"


Стало:
<dataset name="Events">
Select
[Events].[id],
[Events].[Date],
[Persons].[Name] [Person Name],
[Events: Types].[Name] [Event Type Name],
[Events].[Notes]
From [Events]
Left Join [Persons] On [Persons].[id]=[Events].[Person]
Left Join [Events: Types] On [Events: Types].[id]=[Events].[Type]
Where (1=1)
<where expression="[Events].[Person]" condition="=" param="Person"/>
<where expression="[Events].[Type]" condition="=" param="Type"/>
<where expression="[Events].[Date]" condition="&lt;=" param="Date Max"/>
<where expression="[Events].[Date]" condition="&gt;=" param="Date Min"/>
<where expression="[Persons].[Financial Group]" condition="=" param="Financial Group"/>
Order By [Events].[Date]
</dataset>


Определенно стало намного удобнее писать SQL-запросы.

14 декабря 2006

Memory allocation by compiler

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

Fatal Error C1076: compiler limit : internal heap limit reached; use /Zm to specify a higher limit

Компилятору не хватило памяти и он предлагает мне вручную указать ему сколько памяти использовать. То ли я чего не понимаю, но вроде давно изобрели динамическое выделение памяти. Или он не хочет случайно в swap залезть... Да бред какой-то.

12 декабря 2006

Performance issues

Загрузил в свой проект реальные данные и ужаснулся - получил жуткие тормоза. Окно открывалось секунд 10. Нереально долго.

Вначале подумал что SQL запросы тормозят на большом количестве данных. Ан нет, летают. Собрал Release версию. Бегает намного быстрее, но все равно - окно открывает 2-3 секунды. Это конечно лучше чем 10 секунд у Debug версии, но мне 1) в бета-тестирование давать именно Debug версию, 2) самому отлаживат - Debug версию, 3) все равно очень долго.

Методом тыка (без профайлера) выяснил что больше всего тормозит lookup записей из таблиц. Причем в коде стоял простой std::find и комментарий "todo: использовать индексы для быстрого поиска". На тестовых данных все летало, а вот на реальных данных, когда есть 3000 записей к ним нужно lookup-ить 2000 записей получалось O(M*N) = 2000*3000 = 6000000. Реализовал индексирование - стало намного быстрее. Открытие окна - секунды 2. Причем и в debug и в release примерно одинаково.

Пробежал код глазами - обнаружил интересный момент. При загрузке данных с SQL сервера идет преобразование из COM-овского _variant_t в проприетарный CVariant. Строки загружаются очень весело: у CVariant есть метод SetString(const std::wstring&), а у _variant_t достать строку можно только сначала преобразовав его в _bstr_t (при этом строка копируется), затем в const wchar_t*. При передаче ее в SetString() сначала конструируется wstring (а это плюс еще одно копирование), затем идет непосредственно копирование строки внутри CVariant::SetString(). Вобщем куча копирований вместо одного. Придется вручную лезть внутрь _variant_t, благо он унаследован от tagVARIANT, который является структурой - то есть все внутренности - public. Но самое интересное - жуткие тормоза возникают совсем не из-за этого, а где-то в другом месте.

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

Естественно, необходимо реализовать кэш. Да так и планировалось - создать кэш. Но проблема создания такого кэша - его же надо обновлять. В дальнейшем все запросы (в том числе на изменение данных) должны проходить через сервер приложений, и он должен присылать по подписке сообщения типа "обновите кэш". Но сейчас временно (до первой беты) сервера приложений не планируется. Так как если его делать сразу, то хрен знает когда начнется бета-тестирование. А без сервера приложений некому прислать сообщение "обновите кэш" - используемый SQL Server этого не умеет.

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

PS. Как в таких случаях жить без профайлера? VTune $700 стоит. Может что попроще и побесплатней есть?

08 декабря 2006

Last element in a container

Поймал себя на использовании конструкции Container[Container.size()-1] для доступа к последнему элементу контейнера.

Ужаснулся, переписал все через *Container.rbegin() Container.back()

01 декабря 2006

assert

assert - утверждать; заявлять, объявлять, декларировать, провозглашать

assert(Pointer != NULL);
"Я утверждаю что в данной точке программы Pointer не равен нулю и пусть тот, кто считает что я не прав, первым бросит в меня камень исключение!

22 ноября 2006

Build optimization

Решил поискать пути ускорения build-а не меняя аппаратную часть компьютера. Перенес includes из boost на RAM disk. Проект который раньше компилировался пять минут теперь компилируется четыре.

Убил RAM disk, перенес весь используемый boost и стандартную библиотеку в precompiled header. Компиляция того проекта укладывается уже в 2 минуты.

21 ноября 2006

Smart pointers rule

Набрел в сети на Process Explorer, побаловался с ним. Посмотрел сколько мое приложение ест памяти и т.п. Провел эксперимент - в приложении несколько раз открыл-закрыл довольно тяжелое по требованиям к памяти окно.

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

Но, спрашивается, если я везде использую умные указатели, то какого хрена получаю утечки памяти? Это напрягло меня вдвойне. Я никогда в жизни не искал утечки памяти - на C++ пишу чуть больше года, до этого писал на VBA с его COM-объектами и отсутствием заморочек на счет освобождения памяти.

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

После этого я зарекся при поиске утечек обойтись без утилит, а просто прописать в настройках проекта Force Includes некий хедер-файл, заменяющий malloc и free на их отладочные версии запоминающие кто сколько выделил и освободил ли потом.

Но сейчас мне совсем не хотелось с этим мучаться - я ведь просто игрался с Process Explorer. Но утечки покоя мне не давали.

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

Это был следующий код:
std::wstring CXMLNode::OptionalAttr
(
const std::wstring& AttrName,
const std::wstring& DefaultValue
) const
{
assert( !IsNull() );
const xmlChar* const AttrStr =
xmlGetProp( _Node, WideStringToXMLChars( AttrName ).c_str() );
if ( AttrStr != NULL )
{
return XMLCharsToWideString( AttrStr );
}
else
{
return DefaultValue;
}
}
Это был класс C++-оболочки libxml, написанный не мной, а программистом, работавшим на меня. Я конечно, порадовался, что код не мой ;) И сразу очевидно, утечку дает AttrStr = xmlGetProp(...), так как xmlGetProp() - функция из C-шной библиотеки (libxml), а там про умные указатели не знают.

Так и оказалось - в документации к xmlGetProp значилось:
Returns: the attribute value or NULL if not found. It's up to the caller to free the memory with xmlFree().
А мой программист на xmlFree() забил.

Так как xmlGetProp() постоянно использовалась, пришлось написать для нее "smart pointer", чтобы не напрягать мозги на счет xmlFree(). Что-то типа такого:
// XMLAttr helper for automaticaly memory manage
class CXMLAttr
{
public:
CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName);
bool IsValid() const;
const std::wstring& Value() const;

private:
bool _IsValid;
std::wstring _Value;
};

inline CXMLAttr::CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName)
{
std::basic_string Name;
WideStringToXMLChars(AttributeName, Name);

xmlChar* const Attribute = xmlGetProp(Node, Name.c_str());

_IsValid = (Attribute != NULL);

if (Attribute)
{
XMLCharsToWideString(Attribute, _Value);
xmlFree(Attribute);
}
}

inline bool CXMLAttr::IsValid() const
{
return _IsValid;
}

inline const std::wstring& CXMLAttr::Value() const
{
return _Value;
}


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

09 ноября 2006

Вам тултипы какой версии?

Тестируя свое приложение под Windows 2000 обнаружил следующий баг: не работают тултипы. В XP работают, а в Win2k - нет.

Окзалось, засада ждала меня в совершенно безобидном операторе:
toolinfo.cbSize = sizeof(TOOLINFO)

Объясню, почему там была засада. Для начала посмотрим как объявлен TOOLINFO (точнее tagTOOLINFOW, т.к. TOOLINFO - его алиас) в commctrl.h:
typedef struct tagTOOLINFOW {
UINT cbSize;
UINT uFlags;
HWND hwnd;
UINT_PTR uId;
RECT rect;
HINSTANCE hinst;
LPWSTR lpszText;
#if (_WIN32_IE >= 0x0300)
LPARAM lParam;
#endif
#if (_WIN32_WINNT >= 0x0501)
void *lpReserved;
#endif
} TTTOOLINFOW, NEAR *PTOOLINFOW, *LPTTTOOLINFOW;

В параметрах компиляции у меня стоит _WIN32_WINNT = 0x0501, чтобы была возможность использовать все возможности WinXP, такие как XP themes и прочие. Как видно выше, при таких настройках я получаю sizeof(TOOLINFO), учитывающее lpReserved, про который Win2000 не знает. И встретив toolinfo.cbSize больше того размера TOOLINFO, которое известно Win2000, функции WinAPI отказываются работать.

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

Ну да ладно, не проглатывает и пусть. Но как быть? Установить _WIN32_WINNT меньше 0x0501 я не могу. Остается "вручную" (вычесть указатель на tooltip из указателя на tooltip.lpReserved) считать размер TOOLINFO для Win2000 и использовать его. Здесь надо заметить, что сам я sizeof(TOOLINFO) не считаю, так как он за меня считается внутри WTL::CToolInfo::Init() ;-) Выход я, конечно найду (откажусь от WTL::CToolTipCtrlT или внесу в WTL::CToolInfo::Init() поправку), но осадок остается.

01 ноября 2006

Unit tests rule

Сегодня на собственной шкуре убедился в ценности юнит-тестов.

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

Но не задел ли я при этом ничего другого?

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

Не будь этого теста, количество ошибок в модуле осталось бы таким как было, просто одна ошибка сменилась на другую ;)

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