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, и при обращении к объекту просто лочили его.