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)