Бывает обидно когда вылезают баги в том месте, где раньше все работало нормально. Смотришь - баг, но точно помнишь что месяц назад этого бага не было. Причем в какой момент образовался этот баг - непонятно. Столько кода с уже добавлено и переписано.
Решение такой проблемы на корню: обвешивание всего кода юнит-тестами и прогон всех тестов после каждого коммита. Однако не всегда это реализуемо. Либо тесты писать времени нет, либо очень сложно их писать (например, для GUI).
В таком случае можно прибегнуть к двоичному поиску. У нас есть репозиторий и можно получить функцию от номера ревизии - воспроизводится ли данный баг в этой ревизии или нет. Например, в 500-ой его не было а сейчас 1030-ая и он есть. Делим отрезок ревизий [500,1030] пополам, попадаем где-то в 765-ую, апдейтимся до нее, ребилдим проект, смотрим воспроизводится ли баг. Не воспроизводится? Значит он где-то в отрезке [765,1030]. Делим его пополам, и т.д пока не найдем отрезок длинной в один коммит, например [897,898]. Значит 898-ой коммит вызвал появление бага. Смотрим этот коммит и разбираемся в чем дело, выясняем какие изменения привели к багу.
Если коммиты небольшие по объему (а я рекомендую именно так и делать), то довольно быстро можно понять где вылез баг и пофиксить его.
22 августа 2008
08 августа 2008
Typedefs
Кстати, о typedef-ах. Typedef не создает новый тип - только его псевдоним. В этом можно убедиться на простом примере:
Кстати, на счет типов. Как думаете, что выдаст следующий код:
template <typename T> class foo {};В последней строке получим ошибку, говорящую нам что шаблон foo для типа char уже определен.
template <> class foo<char> {};
typedef char bar;
template <> class foo<bar> {} // compile error
Кстати, на счет типов. Как думаете, что выдаст следующий код:
template <typename T> struct foo { static const int x = 1; };Единицу. Char, signed char и unsigned char - три разных типа. В зависимости от настроек компилятора char ведет себя как signed или unsigned, но это отдельный тип.
template <> struct foo<signed char> { static const int x = 2; };
template <> struct foo<unsigned char> { static const int x = 3; };
cout << foo<char>::x;
String parameters
Пытался установить Visual Studio 2005 Service Pack 1 на машине, у которой всего полтора гигабайта свободного места на диске. Думаете получилось? Нифига - места на диске не хватило!
Этот сервис пак представляет собой .exe размером 500Mb, внутри которого .msp, а внутри .msp видимо все обновляемые файлы лежат. То сначала распаковывается содержимое .exe в Temp, потом туда же распаковывается содержимое .msp, и видимо там дальше еще что-то распаковывается... Вобщем, красота.
К чему это я - к тому как часто передаются строки в качестве параметров. Строка в C++ - чаще всего std::basic_string:
Если у нас строка хранится не в std::string, а например в const char*, то получим неявное копирование строки в конструкторе std::string.
Уже лучше - можем передать string.c_str(), можем что угодно другое, где есть null terminator. А если у нас строка (или даже большой текст) загружен из ресурсов и есть const char* data() и size_t size(), но нет замыкающего нуля - снова придется делать копию. И это не единственный вариант, когда есть data()/size() - если хотим передать подстроку (внутреннюю часть какой-либо большой строки), то возникнет та же проблема.
Уже намного лучше. Правда если строка в другой кодировке или вообще, читается с потока - опять нужно складывать копию в память, а потом только вызывать foo().
А хорошая альтернатива - это парочка итераторов (a-la STL) или контейнер (a-la Boost):
Конструкции почти эквивалентны, т.к. у контейнера легко можно взять String.begin() и String.end(), а пару итераторов - скормить boost::make_iterator_range(). Так что скорее вопрос вкуса, какой вариант выбрать - я за контейнер.
С таким интерфейсом мы можем взять свести копирование к минимуму. Например, пришли данные в urlencode, да еще в win1251:
Парсим все в указатели:
Делаем итератор для декодирования urldecode ("%EF%F0%E8%E2%E5%F2" -> "привет") - и его operator* и operator++ будут делать всю работу "на ходу". (Тут можно призвать на помощь boost::iterator_facade). Нужно конвертировать в utf-8? - еще один итератор ;) И все это скормим нашей foo().
PS. Подробности такого подхода - в boost.iterator, boost.range, boost.string algo.
Этот сервис пак представляет собой .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
Типичный код отрисовки окна:
Типичный паттерн чтобы этого избежать —рисовать во время обратного хода луча использовать 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);
...
}
Типичный паттерн чтобы этого избежать —
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;
};
Подписаться на:
Сообщения (Atom)