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

31 декабря 2006

Properties in C++

Замечательная штука есть в современных языках программирования - properties. Берешь и пишешь: Object.Visible = True и объект отображается на экране. Хочешь проверить, отображен ли объект - пишешь If Object.Visible Then...

Все так просто! Visible - это свойство объекта "быть показанным на экране". Мы можем легко читать и изменять это свойство зная его имя.

В C++ для этого необходимо сделать accessor и mutator, то есть две функции: одна позволяющая получить значение, вторая - изменяющая значение (со всеми последствиями для объекта). Если accessor можно назвать именем свойства - bool Visible() - то как быть с mutator-ом? Назвать его Visible(bool)? Не красиво. SetVisible(bool)? Не очень понятно. Show(bool)? А как запомнить что это mutator к свойству Visible?

Более красивый вариант - прикрутить properties к C++. Загоревшись этой идеей набросал шаблон property:
template <typename T, class C>
class property
{
public:
typedef T (C::*accessor_t)() const;
typedef void (C::*mutator_t)(const T&);

property(C* object, accessor_t accessor, mutator_t mutator) :
_object(object), _accessor(accessor), _mutator(mutator) {}

operator T() const { return ((*_object).*(_accessor))(); }
property& operator=(const T& value) { ((*_object).*(_mutator))(value); return *this; }

private:
C* _object;
accessor_t _accessor;
mutator_t _mutator;
};

class range
{
public:
range(int begin, int end) : _begin(begin), _end(end) {}

int begin() const { return _begin; }
int end() const { return _end; }

public:
property<int, range> size() { return property<int, range>(this, size, setsize); }

int size() const { return _end - _begin; }
void setsize(const int& size) { _end = _begin + size; }

private:
int _begin, _end;
};

void TestProperties()
{
range r(1, 5);
cout << "range is [" << r.begin() << "," << r.end() << ") size=" << r.size() << endl;

r.size() = 2;
cout << "range is [" << r.begin() << "," << r.end() << ") size=" << r.size() << endl;
}

29 декабря 2006

Dynamicaly created SQL statements

Переписываю приложение с VBA на C++. В приложении во всю используются динамически создаваемые SQL-запросы:

Было:
Условие = ""
If VarType(Me![Начальная дата]) > vbNull Then ДобавитьУсловие Условие, "[Дата] >= " & ДатаДляSQL(Me![Начальная дата])
If VarType(Me![Конечная дата]) > vbNull Then ДобавитьУсловие Условие, "[Дата] <= " & ДатаДляSQL(Me![Конечная дата])
If VarType(Me![Тип]) > vbNull Then ДобавитьУсловие Условие, "[Тип] = " & Me![Тип]
If VarType(Me![Эмитент]) > vbNull Then ДобавитьУсловие Условие, "[Эмитент] = " & Me![Эмитент]
If VarType(Me![Структура]) > vbNull Then ДобавитьУсловие Условие, "[Структура] = " & Me![Структура]
Me![Список].RowSource = "SELECT * FROM [События - список]" & OptionalWhereStatement(Условие) & " ORDER BY [Дата], [Название общества]"


Стало:
<dataset name="Events">
Select
[Events].[id],
[Events].[Date],
[Persons].[Name] [Person Name],
[Events: Types].[Name] [Event Type Name],
[Events].[Notes]
From [Events]
Left Join [Persons] On [Persons].[id]=[Events].[Person]
Left Join [Events: Types] On [Events: Types].[id]=[Events].[Type]
Where (1=1)
<where expression="[Events].[Person]" condition="=" param="Person"/>
<where expression="[Events].[Type]" condition="=" param="Type"/>
<where expression="[Events].[Date]" condition="&lt;=" param="Date Max"/>
<where expression="[Events].[Date]" condition="&gt;=" param="Date Min"/>
<where expression="[Persons].[Financial Group]" condition="=" param="Financial Group"/>
Order By [Events].[Date]
</dataset>


Определенно стало намного удобнее писать SQL-запросы.

14 декабря 2006

Memory allocation by compiler

Получил занимательное сообщение от компилятора:

Fatal Error C1076: compiler limit : internal heap limit reached; use /Zm to specify a higher limit

Компилятору не хватило памяти и он предлагает мне вручную указать ему сколько памяти использовать. То ли я чего не понимаю, но вроде давно изобрели динамическое выделение памяти. Или он не хочет случайно в swap залезть... Да бред какой-то.

12 декабря 2006

Performance issues

Загрузил в свой проект реальные данные и ужаснулся - получил жуткие тормоза. Окно открывалось секунд 10. Нереально долго.

Вначале подумал что SQL запросы тормозят на большом количестве данных. Ан нет, летают. Собрал Release версию. Бегает намного быстрее, но все равно - окно открывает 2-3 секунды. Это конечно лучше чем 10 секунд у Debug версии, но мне 1) в бета-тестирование давать именно Debug версию, 2) самому отлаживат - Debug версию, 3) все равно очень долго.

Методом тыка (без профайлера) выяснил что больше всего тормозит lookup записей из таблиц. Причем в коде стоял простой std::find и комментарий "todo: использовать индексы для быстрого поиска". На тестовых данных все летало, а вот на реальных данных, когда есть 3000 записей к ним нужно lookup-ить 2000 записей получалось O(M*N) = 2000*3000 = 6000000. Реализовал индексирование - стало намного быстрее. Открытие окна - секунды 2. Причем и в debug и в release примерно одинаково.

Пробежал код глазами - обнаружил интересный момент. При загрузке данных с SQL сервера идет преобразование из COM-овского _variant_t в проприетарный CVariant. Строки загружаются очень весело: у CVariant есть метод SetString(const std::wstring&), а у _variant_t достать строку можно только сначала преобразовав его в _bstr_t (при этом строка копируется), затем в const wchar_t*. При передаче ее в SetString() сначала конструируется wstring (а это плюс еще одно копирование), затем идет непосредственно копирование строки внутри CVariant::SetString(). Вобщем куча копирований вместо одного. Придется вручную лезть внутрь _variant_t, благо он унаследован от tagVARIANT, который является структурой - то есть все внутренности - public. Но самое интересное - жуткие тормоза возникают совсем не из-за этого, а где-то в другом месте.

Это другое место оказалось повторной загрузкой одних и тех же данных. То есть если какому-то объекту понадобились какие-то данные, то он их просто грузит с сервера. И так несколько объектов вытягивают одну и ту же большую таблицу.

Естественно, необходимо реализовать кэш. Да так и планировалось - создать кэш. Но проблема создания такого кэша - его же надо обновлять. В дальнейшем все запросы (в том числе на изменение данных) должны проходить через сервер приложений, и он должен присылать по подписке сообщения типа "обновите кэш". Но сейчас временно (до первой беты) сервера приложений не планируется. Так как если его делать сразу, то хрен знает когда начнется бета-тестирование. А без сервера приложений некому прислать сообщение "обновите кэш" - используемый SQL Server этого не умеет.

Видимо придется пока временно делать таймер, который будет периодически наведываться на SQL сервер и узнавать, не пора ли обновить кэш.

PS. Как в таких случаях жить без профайлера? VTune $700 стоит. Может что попроще и побесплатней есть?

08 декабря 2006

Last element in a container

Поймал себя на использовании конструкции Container[Container.size()-1] для доступа к последнему элементу контейнера.

Ужаснулся, переписал все через *Container.rbegin() Container.back()

01 декабря 2006

assert

assert - утверждать; заявлять, объявлять, декларировать, провозглашать

assert(Pointer != NULL);
"Я утверждаю что в данной точке программы Pointer не равен нулю и пусть тот, кто считает что я не прав, первым бросит в меня камень исключение!

22 ноября 2006

Build optimization

Решил поискать пути ускорения build-а не меняя аппаратную часть компьютера. Перенес includes из boost на RAM disk. Проект который раньше компилировался пять минут теперь компилируется четыре.

Убил RAM disk, перенес весь используемый boost и стандартную библиотеку в precompiled header. Компиляция того проекта укладывается уже в 2 минуты.

21 ноября 2006

Smart pointers rule

Набрел в сети на Process Explorer, побаловался с ним. Посмотрел сколько мое приложение ест памяти и т.п. Провел эксперимент - в приложении несколько раз открыл-закрыл довольно тяжелое по требованиям к памяти окно.

Ну меня это, естественно, сильно напрягло. Ведь означало это скорее всего только одно - утечки памяти. А я их жутко не люблю, что даже ввел в стандарт кодирования повсеместное использование умных указателей.

Но, спрашивается, если я везде использую умные указатели, то какого хрена получаю утечки памяти? Это напрягло меня вдвойне. Я никогда в жизни не искал утечки памяти - на C++ пишу чуть больше года, до этого писал на VBA с его COM-объектами и отсутствием заморочек на счет освобождения памяти.

Однажды я попробовал некую небольшую утилиту для поиска памяти - она встраивалась в Visual Studio и по завершении приложения выдавала список утечек. Так она мне их выдало такое немерянное количество в моем приложении, что я в это не поверил и снес ее нафиг.

После этого я зарекся при поиске утечек обойтись без утилит, а просто прописать в настройках проекта Force Includes некий хедер-файл, заменяющий malloc и free на их отладочные версии запоминающие кто сколько выделил и освободил ли потом.

Но сейчас мне совсем не хотелось с этим мучаться - я ведь просто игрался с Process Explorer. Но утечки покоя мне не давали.

В результате я просто добавил код, 2000 раз открывающий и закрывающий в приложении определенное окно и смотрел на прирост сожранной памяти в Process Explorer. Постепенно убирая функционал из кода, создающего окно, я добился того, что память перестала пожираться. Соответственно, последний убранный код был кодом, дающим утечки памяти.

Это был следующий код:
std::wstring CXMLNode::OptionalAttr
(
const std::wstring& AttrName,
const std::wstring& DefaultValue
) const
{
assert( !IsNull() );
const xmlChar* const AttrStr =
xmlGetProp( _Node, WideStringToXMLChars( AttrName ).c_str() );
if ( AttrStr != NULL )
{
return XMLCharsToWideString( AttrStr );
}
else
{
return DefaultValue;
}
}
Это был класс C++-оболочки libxml, написанный не мной, а программистом, работавшим на меня. Я конечно, порадовался, что код не мой ;) И сразу очевидно, утечку дает AttrStr = xmlGetProp(...), так как xmlGetProp() - функция из C-шной библиотеки (libxml), а там про умные указатели не знают.

Так и оказалось - в документации к xmlGetProp значилось:
Returns: the attribute value or NULL if not found. It's up to the caller to free the memory with xmlFree().
А мой программист на xmlFree() забил.

Так как xmlGetProp() постоянно использовалась, пришлось написать для нее "smart pointer", чтобы не напрягать мозги на счет xmlFree(). Что-то типа такого:
// XMLAttr helper for automaticaly memory manage
class CXMLAttr
{
public:
CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName);
bool IsValid() const;
const std::wstring& Value() const;

private:
bool _IsValid;
std::wstring _Value;
};

inline CXMLAttr::CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName)
{
std::basic_string Name;
WideStringToXMLChars(AttributeName, Name);

xmlChar* const Attribute = xmlGetProp(Node, Name.c_str());

_IsValid = (Attribute != NULL);

if (Attribute)
{
XMLCharsToWideString(Attribute, _Value);
xmlFree(Attribute);
}
}

inline bool CXMLAttr::IsValid() const
{
return _IsValid;
}

inline const std::wstring& CXMLAttr::Value() const
{
return _Value;
}


Восстановил функциональность приложения, прогнал 2000 открытий-закрытий, перерасхода памяти не обнаружил.

09 ноября 2006

Вам тултипы какой версии?

Тестируя свое приложение под Windows 2000 обнаружил следующий баг: не работают тултипы. В XP работают, а в Win2k - нет.

Окзалось, засада ждала меня в совершенно безобидном операторе:
toolinfo.cbSize = sizeof(TOOLINFO)

Объясню, почему там была засада. Для начала посмотрим как объявлен TOOLINFO (точнее tagTOOLINFOW, т.к. TOOLINFO - его алиас) в commctrl.h:
typedef struct tagTOOLINFOW {
UINT cbSize;
UINT uFlags;
HWND hwnd;
UINT_PTR uId;
RECT rect;
HINSTANCE hinst;
LPWSTR lpszText;
#if (_WIN32_IE >= 0x0300)
LPARAM lParam;
#endif
#if (_WIN32_WINNT >= 0x0501)
void *lpReserved;
#endif
} TTTOOLINFOW, NEAR *PTOOLINFOW, *LPTTTOOLINFOW;

В параметрах компиляции у меня стоит _WIN32_WINNT = 0x0501, чтобы была возможность использовать все возможности WinXP, такие как XP themes и прочие. Как видно выше, при таких настройках я получаю sizeof(TOOLINFO), учитывающее lpReserved, про который Win2000 не знает. И встретив toolinfo.cbSize больше того размера TOOLINFO, которое известно Win2000, функции WinAPI отказываются работать.

Я, однако, ожидал, что WinAPI проглатывает такие структуры когда cbSize > известного текущей версии WinAPI размера структуры, а cbSize нужно наоборот, для того, чтобы более поздние версии WinAPI не пытались читать за границами структур, передаваемых старыми программами.

Ну да ладно, не проглатывает и пусть. Но как быть? Установить _WIN32_WINNT меньше 0x0501 я не могу. Остается "вручную" (вычесть указатель на tooltip из указателя на tooltip.lpReserved) считать размер TOOLINFO для Win2000 и использовать его. Здесь надо заметить, что сам я sizeof(TOOLINFO) не считаю, так как он за меня считается внутри WTL::CToolInfo::Init() ;-) Выход я, конечно найду (откажусь от WTL::CToolTipCtrlT или внесу в WTL::CToolInfo::Init() поправку), но осадок остается.

01 ноября 2006

Unit tests rule

Сегодня на собственной шкуре убедился в ценности юнит-тестов.

В одном модуле обнаружилась небольшая ошибка. Не откладывая в долгий ящик полез ее фиксить. Поглядел код модуля, внес изменения. Теперь в том случае, в котором выявилась ошибка, стало все нормально.

Но не задел ли я при этом ничего другого?

Как хорошо, что у этого модуля был полноценный юнит-тест (да, конечно, при его создании прозевали тест, который выявил бы ту ошибку, из-за которой я и полез править код, но все же тест был довольно всеобъемлющий). И запустив этот тест сразу же обнаружилась новая ошибка, которую я привнес своими исправлениями. Переосмыслив код еще раз, я убедился (запустив тест), что все пофиксил.

Не будь этого теста, количество ошибок в модуле осталось бы таким как было, просто одна ошибка сменилась на другую ;)

PS. Кстати, первым делом перед внесением каких-либо изменений в модуль я, конечно, добавил в этот тест данные, воспроизводящие обнаружившуюся ошибку. Убедился, что тест проваливается, и только тогда полез смотреть код и править его.

31 октября 2006

Отбросьте ваши тени в сторону

В минимальных требованиях к окружению (харду и софту) разрабатываемой мною системы стоит "Windows 2000 и выше". Так что приходится тестировать систему под Win2k. О некоторых таких приключениях я уже писал, а недавно Win2k ввел меня в очередной ступор.

При нажатии на кнопку, раскрывающую список combobox'а, всегда вылетаем по assert(hwnd == NULL). Под WinXP все отлично. Тут надо заметить, что Win2k стоит у меня на виртуальной машине на ноуте. На эту виртуальную машину для отладки пришлось поставить Visual Studio (предварительно установив кучу всяких IE Service Pack, .NET, etc, которые требовал VS).

Проект компилировался на виртуальной машине минут 30-60!!! Оказалось что не может зарегистрироваться оконный класс раскрывающегося списка combobox-а, причем GetLastError() выдает "Недопустимый параметр". Какой параметр - не говорит. Кстати, ради добавления этого самого GetLastError() в код его пришлось перекомпилировать - а это опять полчаса дикого жужжания вентилятора процессора (на ноуте у меня Celeron M 1.4).

Класс не регистрировался из-за стиля CS_DROPSHADOW, которого видимо нет на Win2000. Вот так все оказалось просто. А времени на отладку ушло просто офигеть сколько.

PS. Вообще надо памятник поставить изобретателям assert()-ов. Мега-полезная вещь.

30 октября 2006

Как легко и не принужденно держать себя за волосы

В моем проекте в части GUI написаны собственные классы типа CEdit, CButton и etc, которые имеют интерфейсы, используемые бизнес-логикой. Непосредственно реализация этих контролов представлена в классах типа CEditImpl, CButtonImpl и т.п., которые наследуются от ATL::CWindow. Эти C*Impl содержатся в виде private членов внутри соответствующих C*.

Все было отлично. Но вот по событию "нажатие кнопки", приходящему синхронно от CButton в бизнес-логику, эта бизнес-логика закрывает окно, в котором содержится кнопка и все рушится по assert-у в недрах ATL::CWindow. Фигня вся в том, что все это происходит в обработчике WM_LBUTTONDOWN, а закрытие окна ведет к удалению объекта этого самого окна, которое грохает коллекцию его контролов (CEdit, CButton, ...), они в свою очередь грохают объекты CEditImpl, CButtonImpl, ... А деструктор класса ATL::CWindow (от него унаследованы все CButtonImpl) ругается на то, что виндовый контрол "кнопка" еще не уничтожен - внутри его обработчика WM_LBUTTONDOWN мы сейчас сидим.

В ATL/WTL безопасно можно грохать оконный объект в виртуальной OnFinalMessage(), то есть объект CButtonImpl должен пережить CButton, а в CButtonImpl::OnFinalMessage() прибить себя. Но все же CButton тоже должен уметь убивать CButtonImpl, например если виндовый контрол еще не был создан - в таком случае до CButtonImpl::OnFinalMessage() дело не дойдет.

Вобщем было много головной боли по поводу того кто и когда должен грохнуть объект CButtonImpl: то ли сам CButtonImpl, то ли CButton, они должны между собой согласовать действия чтобы:
1. не упать по assert-у в недрах деструктора ATL::CWindow
2. все же удалить CButtonImpl
3. не удалить его несколько раз ;)

Вначале я написал море кода, которое спотыкалось то тут, то там. Но вскоре удалил его нафиг, так как понял что изобретаю велосипед.

Решение получилось примерно такое:
class CButtonImpl : ATL::CWindow...
{
public:
void OnFinalMessage() { SharedPointer.release(); }

boost::shared_ptr<CButtonImpl> SharedPointer;
}

class CButton
{
public:
CButton() : _ButtonImpl(new CButtonImpl)
{
}

void Create()
{
_Button->Create();
_Button->SharedPointer = _Button;
}

private:
boost::shared_ptr<CButtonImpl> _ButtonImpl;
};

То есть условно говоря, CButtonImpl держит себя за волосы пока он существует как виндовый контрол.

Ссылка по теме: delete this

26 октября 2006

Easy constructors

Иногда необходимо расширить поведение существующего класса - добавить метод, изменить метод.

В простейшем случае все выглядит достаточно просто:
class Foo : public Bar
{
public:
void NewMember();
}

Однако в таком случае унаследованы все публичные методы, но не конструкторы Bar (разве что кроме конструктора по умолчанию). Вручную "наследовать" все конструкторы может быть довольно утомительно, поможет парочка шаблонных конструкторов:
class Foo : public Bar
{
public:
Foo() : Bar() {}
template <typename T> Foo(T t) : Bar(t) {}
template <typename T1, typename T2> Foo(T1 t1, T2 t2) : Bar(t1, t2) {}
...

void NewMember();
}

11 октября 2006

Precompiled headers fun

Сегодня упорно боролся с компилятором. Ну не видет он некоторые классы и всё тут, хотя нужные header-файлы указаны.
#include "Event.h"
#include "Control.h"
Event event;
Вот на последней строчке он и ругается - не знаю я ваш Event и все тут.

После некоторого времени ковыряний до мнея дошла замечательная мысль - файл Control.h указан как файл, по которому нужно использовать precompiled header.

То есть встречая Control.h компилятор грузит precompiled header и напрочь забывает про header-ы, которые были до него.

Поставил #include "Control.h" первой строкой - компилятор стал доволен.

10 октября 2006

XP Themes and Windows 2000

В текущем у меня проекте активно используются XP-темы и для простоты их использования я создал небольшую иерархию классов:
CSkin - CBaseSkin - CXPThemeSkin

CSkin представляет из себя интерфейс, CBaseSkin рисует без XP-тем (то есть вполне должен выполняться на Windows 2000), CXPThemeSkin соответственно рисует с помощью XP-тем (если не удается - рисует с помощью своего базового класса - CBaseSkin).

CXPThemeSkin использует всякие OpenThemeData(), CloseThemeData() из uxtheme.dll, CBaseSkin ничего этого не использует.

При старте программа определяет версию Windows и при WinXP создает объект CXPThemeSkin, в противном случае - CBaseSkin.

И я наивно полагал, что это будет работать на Windows 2000. А вчера поставил Win2k на виртуальную машину и... разочаровался - при старте приложение вылетало с ошибкой "Unable to find uxtheme.dll". Самое интересное, что дело не доходило даже до WinMain() и создания объекта CxxxSkin. То есть XP-темы еще не использовались, а приложение уже вылетало.

При разборе полетов выяснил, что любое упоминание в программе функций из uxtheme.dll приводит к ее загрузке uxtheme.dll, даже такой безобидный код:
void foo()
{
HTHEME Theme = OpenThemeData(NULL, NULL);
}

void WindMain(...)
{
return 0; // do not call foo()
}

Я уж было предполагал, что придется отказаться от простых вызовов OpenThemeData() и подобных, и переписать весь код с использованием LoadLibrary("uxtheme.dll"), вручную получением указателей на функции типа OpenThemeData().

Оказалось, все гораздо проще: достаточно указать в настройках проекта
Linker/Input/Delay Loaded DLLs: uxtheme.dll

01 октября 2006

Edit_SelectAll()

Казалось бы, простая задача - при получении фокуса edit box'ом выделить в нем весь текст. У стандартного edit-а в Windows есть даже такое сообщение EM_SETSEL, которое позволяет выделить любую часть текста. Можно и весь текст выделить. Только при любом выделении текста через EM_SETSEL курсор встает после выделения. Таким образом если текст в edit-е введен длинный - то мы увидим хвост этого текста, а не начало. А такой вариант - полный идиотизм: например, при переходе в ячейку, в которую введено "Иванов Петр Григорьевич" мы увидим [етр Григорьевич] вместо [Иванов Петр Гр], хотя последний вариант намного информативный. К слову сказать, в Microsoft Office сделано правильно, но там свои контролы, а у нас - стандартный edit.

Облазив DejaNews Google.Groups я понял, что цивилизоанного способа попросить стандартный edit поставить курсор в начале выделения люди так и не нашли. Пришлось написать свой трехколесный велосипед:
1. Ставим курсор в конец текста
2. Нажимаем Shift-Home

Как я не люблю такие извращения... Давно пора написать свой edit control или перейти на Scintilla.
void Edit_SelectAll(HWND Edit)
{
ATLASSERT(::IsWindow(Edit));

// Move cursor to the end of text
int Len = ::GetWindowTextLength(Edit);
::SendMessage(Edit, EM_SETSEL, Len, Len);

// Set [Shift] key state as pressed
BYTE KeyboardState[256];
::GetKeyboardState(KeyboardState);
BYTE ShiftState = KeyboardState[VK_SHIFT];
KeyboardState[VK_SHIFT] = ShiftState | 0x80;
::SetKeyboardState(KeyboardState);

// Press [Home] key
::SendMessage(Edit, WM_KEYDOWN, VK_HOME, 0x01470001);

// Restore [Shift] key state
KeyboardState[VK_SHIFT] = ShiftState;
::SetKeyboardState(KeyboardState);
}

28 сентября 2006

Calling base class virtual method

Предположим, у нас есть иерархия классов:
class Base
{
public:
virtual void Do() { cout << "Base::Do()" << endl; }
virtual ~Base() {}
};

class Derived : public Base
{
public:
virtual void Do() { cout << "Derived::Do()" << endl; }
};
Класс Derived переопределяет метод Do(), и код, оперирующий объектом этого класса, при вызове Do() будет всегда вызвать Base::Do().

Даже если преобразовать указатель или ссылку на объект класса Derived к указателю или ссылке на Base, то будет вызываться Derived::Do() - за это отвечает таблица виртуальных функций или другой подобный механизм, встроенный в язык:
Derived d;
d.Do(); // Derived::Do()
Base& b = d;
b.Do(); // тоже Derived::Do()

А теперь вопрос: как вызвать Base::Do() для объекта типа Derived?

Во-первых, разберемся корректно ли это. По сути, void Base::Do() представляет собой void Do(Base* This), а void Derived::Do() представляет void Do(Derived* This). Так как можно легко преобразовывать Derived* к Base*, то есть вызов Base::Do() для Derived является корректным. Такой вызов часто используется при напимании методов унаследанного класса:
void Derived::DoSomething()
{
// do something
Base::Do();
// do something else
}
Прекрасно, "изнутри" Derived (то есть из методов самого Derived) мы можем вызывать Base::Do(), но как быть если нужно вызвать этот метод "снаружи"?

Оказывается, синтаксис такой:
Derived d;
d.Base::Do();

19 сентября 2006

Is "for each" possible with current C++?

Всем поклонникам C++ советую прочитать эту статью: Conditional Love: FOREACH Redux, написанную одним из C++-geek-ов - Эриком Ниблером из Boost Consulting.

Вы поймете какую радость приносит людям язык C++ с кучей своих недостатков и нереализованных в нем возможностей. Сможете ли Вы написать макрос FOREACH(item, container)? Будет ли он нормально работать с любыми контейнерами, в том числе и с rvalue? Возможно ли вообще его написать на современном C++ не дожидаясь выхода новых стандартов?

Полученный Эриком FOREACH, по-моему, представляет чисто академический интерес (зато какой интерес!). У меня и так время компиляции - узкое место, а с такими конструкциями мой компилятор вообще коньки отбросит наверно...

PS. А вообще эта статья про мой любимый условный оператор ?:

09 сентября 2006

Эволюция WinAPI

Каким образом нужно использовать ту или иную возможность WinAPI - сильно зависит от времени, когда она появилась. Если она была введена в Win3.1 - получите доступ через сообщения, если в Win2000 - через COM объекты. Полагаю, новые возможности Vista будут доступны только через .NET.

Пример - настройка combobox. Показывать в нем свои элементы можно давно, и реализовывается это через обработку сообщений (WM_DRAWITEM). Сделать собственное автодополнение и автозаполнение - сравнительно недавно, и делать это надо уже через написание COM-объекта (IAutoComplete). Новые возможности комбобокса, думаю будут доступны через managed code.