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[] применялась только к строкам в которых шел поиск. Этот финт, правда, почти не сказался на скорости.