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 не равен нулю и пусть тот, кто считает что я не прав, первым бросит в меня камень исключение!