22 августа 2008

Debugging using binary search

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

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

В таком случае можно прибегнуть к двоичному поиску. У нас есть репозиторий и можно получить функцию от номера ревизии - воспроизводится ли данный баг в этой ревизии или нет. Например, в 500-ой его не было а сейчас 1030-ая и он есть. Делим отрезок ревизий [500,1030] пополам, попадаем где-то в 765-ую, апдейтимся до нее, ребилдим проект, смотрим воспроизводится ли баг. Не воспроизводится? Значит он где-то в отрезке [765,1030]. Делим его пополам, и т.д пока не найдем отрезок длинной в один коммит, например [897,898]. Значит 898-ой коммит вызвал появление бага. Смотрим этот коммит и разбираемся в чем дело, выясняем какие изменения привели к багу.

Если коммиты небольшие по объему (а я рекомендую именно так и делать), то довольно быстро можно понять где вылез баг и пофиксить его.

08 августа 2008

Typedefs

Кстати, о typedef-ах. Typedef не создает новый тип - только его псевдоним. В этом можно убедиться на простом примере:
template <typename T> class foo {};
template <> class foo<char> {};
typedef char bar;
template <> class foo<bar> {} // compile error
В последней строке получим ошибку, говорящую нам что шаблон foo для типа char уже определен.

Кстати, на счет типов. Как думаете, что выдаст следующий код:
template <typename T> struct foo { static const int x = 1; };
template <> struct foo<signed char> { static const int x = 2; };
template <> struct foo<unsigned char> { static const int x = 3; };
cout << foo<char>::x;
Единицу. Char, signed char и unsigned char - три разных типа. В зависимости от настроек компилятора char ведет себя как signed или unsigned, но это отдельный тип.

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(...); }
};