25 декабря 2008

this == NULL. WTF?

Что вы подумаете, если встретите код типа такого:
if (this == NULL)
...;
Нормально ли что this может быть нулевым, ведь при этом даже к членам класса обратиться нельзя, не то что вызвать виртуальную функцию.
Однако тех, кого не удивляет конструкции вида delete this;, не удивит и проверка this на NULL. В самом деле, this - это обычный указатель, и как любой другой указатель он может быть равен NULL:
class foo {
public:
bool am_i_alive() { return this != NULL; }
};

foo *p = NULL;
assert(!p->am_i_alive());

Но не разумнее ли, спрашивается, проверять указатель на NULL "снаружи" класса (в данном примере - проверять p на NULL), а не внутри его. То есть не вызывать методы объекта, если указатель на него нулевой. И так действительно будет разумнее:
void foo(Bar &bar, IFilter *filter = NULL)
{
if (filter)
filter->init(bar);

for (auto b : bar)
if (!filter || filter->is_accepted(b))
out.insert(b);
}
Единственное преимущество внесения проверки внутрь класса - сокращение вызывающего кода, немного повышающее его написание и читаемость. То есть по сути "синтаксический сахар":
void foo(Bar &bar, IFilter *filter = NULL)
{
filter->init(bar);

for (auto b : bar)
if (filter->is_accepted(b))
out.insert(b);
}
Но ценой этого будет то, что вызываемые функции не должны быть виртуальными, то есть придется каждую виртуальную функцию "обернуть" примерно так:

class IFilter
{
public:
void init(Bar& bar) { if (this) init_impl(bar; }
bool is_accepted(Bar::value_type b) { return !this || is_accepted_impl(b); }
private:
virtual void init_impl(Bar&) = 0;
virtual bool is_accepted_impl(Bar::value_type) = 0;
};

Любая книжка по рефакторингу скажет, что добиться подобного кода можно с помощью определеннго паттерна, который предлагает замену нулевых указателей на указатели ненулевые, но указывающие на объект-пустышку:

class IFilter
{
public:
virtual void init(Bar&) = 0;
virtual bool is_accepted(Bar::value_type) = 0;
};

class NullFilter
{
public:
virtual void init(Bar&) {}
virtual bool is_accepted(Bar::value_type) { return true; }
};

void foo(Bar &bar, IFilter &filter = NullFilter())
{
filter.init(bar);

for (auto b : bar)
if (filter.is_accepted(b))
out.insert(b);
}


В качестве члена какого-нибудь класса это может выглядеть так:
class Foo
{
public:
Foo() : filter(new NullFilter) {}
void set_filter(auto_ptr<IFilter> f) { filter = f; }
void use_filter()
{
// можно смело использовать конструкции filter.get()->xxx()
}
private:
auto_ptr<IFilter> filter;
};
Единственное, что может заставить не послушаться книжку по рефакторингу, так это профайлер ;)

17 декабря 2008

Logical XOR operator in C++

Понадобилось использовать оператор logical XOR и... поначалу не нашел я его в С++. В смысле не нашел оператора, на котором написано "logical XOR". А вообще оператор есть: !=.

18 ноября 2008

The magic of shared_ptr

В качестве еще одной иллюстрации к предыдущей заметке про "магию shared_ptr", в ответ один из комментариев - небольшой пример: создадим простую иерархию классов, где базовый класс не имеет виртуального деструктора.
struct A { ~A() { cout << "~A()" << endl; } };
struct B : public A { ~B() { cout << "~B()" << endl; } };
А теперь создадим объект класса B и поместим его в контейнер указателей на A:
{
auto_ptr<B> b(new B);
ptr_vector<A> x;
x.push_back(b);
}

{
auto_ptr<B> b(new B);
vector< shared_ptr<A> > x;
x.push_back(b);
}
В случае контейнера, хранящего только указатели (в данном случае - boost::ptr_vector) - получим при разрушении объекта вызов только ~A(), а в случае с shared_ptr будет вызван ~B(), так как shared_ptr немного умнее обычного указателя.

17 ноября 2008

Storing dynamicly created objects in a container

Хранение полиморфных объектов в контейнере - классика жанра:
class A { ... };
class B : public A { ... };
std::vector<A*> BunchOfObjects;
BunchOfObjects.push_back(new B);
Главное - не забыть уничтожить удаляемые из контейнера объекты. И если делать это через оператор delete - еще про виртуальный деструктор у базового класса не забыть бы.

Хотя... другое классическое решение позволяет обойтись и без первого и без второго:
std::vector< boost::shared_ptr<A> > BunchOfObjects;
BunchOfObjects.push_back(boost::shared_ptr<A>(new B));
Наличие виртуального деструктора в таком случае не принципиально - shared_ptr запоминает как нужно удалять объект, по умолчанию это будет оператор delete для нужного типа. Таким образом можно хранить, например, список динамически созданных окон - предков ATL::CWindow. Более того, если от хранимых объектов нужна лишь возможность их в нужный момент удалить, то можно вообще не задумываться от их типе используя boost::shared_ptr<void>.

13 ноября 2008

Wake me up tomorrow

Функции WinAPI, которые можно использовать в качестве "будильника" - SetTimer(), WaitForSingleObject(), и прочие принимают время относительное, а не абсолютное. Нельзя сказать "Разбудите меня завтра!". Только "Пните меня через 13786 миллисекунд".

Разумный вариант - подсчитать сколько миллисекунд до заданного времени, но ничто не вечно под луной. Время может перевести пользователь, программка, синхронизирующаяся с сервером, да сама Windows, в конце концов. Так что заодно мониторим WM_TIMECHANGE и пересчитываем сколько осталось.
void SetTimerToTommorrow()
{
using namespace boost::posix_time;
using namespace boost::gregorian;

ptime Now(second_clock::local_time()), Tomorrow(Now.date() + days(1));
SetTimer(..., (Tomorrow - Now).total_milliseconds() + 1, ...);
}

14 октября 2008

Fading instead of cutting

Если текст не влезает в отведенное ему пространство на экране, можно просто его отрезать по краю области. Однако часто будет непонятно что текст обрезан - не очень очевидно что за "Пушкин" скрывается обрезанная "Пушкинская". Решение получше - добавить в конце троеточие: "Пушки...". Но тоже иногда забавно смотрится.

Красивый вариант - уходить в полупрозрачность:


Реализовать можно довольно просто:
  • перед отрисовкой текста запоминаем тот фрагмент фона, на котором текст будет уходить в полупрозрачность
  • после отрисовки текста выводим этот фрагмент с учетом полупрозрачности.

Для удобства использования этих двух процессов (который "перед" и который "после") вынесем первый в конструктор класса Fader, второй в деструктор. Использование будет такое:
{
Fader fader(dc, rect); // выполняется то, что "перед"
SomeDrawing(dc, rect); // непосредственно отрисовка текста
// выполняется то, что "после" - в деструкторе ~Fader()
}

Сама реализация предельно проста:
class Fader
{
public:
Fader::Fader(CDCHandle DC, const CRect& ClientRect) : DC_(DC), FaderRect_(ClientRect)
{
// опеределяем область, в которой будем уходить в полупрозрачность
const int FaderWidth = 32;
FaderRect_.left = max(FaderRect_.right - FaderWidth, 0);

// создаем bitmap
BYTE *Bits = static_cast(CreateBGRABitmap(Bitmap_, DC, FaderRect_.Size()));
if (!Bits)
return;

// копируем фон в bitmap
BitmapDC_.CreateCompatibleDC(DC);
BitmapDC_.SelectBitmap(Bitmap_);
BitBlt(BitmapDC_, 0, 0, FaderRect_.Width(), FaderRect_.Height(), DC, FaderRect_.left, FaderRect_.top, SRCCOPY);

// уводим в полупрозрачность
GdiFlush();
for (int Line = FaderRect_.Height(); Line; --Line)
for (int Pos = 0; Pos < FaderRect_.Width(); ++Pos, Bits += 4)
{
BYTE Alpha = Pos << 3; // как удачно, что FaderWidth является степенью двойки ;)
ApplyAlpha(Bits[0], Alpha);
ApplyAlpha(Bits[1], Alpha);
ApplyAlpha(Bits[2], Alpha);
Bits[3] = Alpha;
}
}

Fader::~Fader()
{
// накладываем полупрозрачный bitmap с фоном
if (BitmapDC_)
AlphaBlend(DC_, FaderRect_, BitmapDC_, CRect(CPoint(0, 0), FaderRect_.Size()));
}

private:
HDC DC_;
CRect FaderRect_;
CBitmap Bitmap_;
CDC BitmapDC_;
};

PS. Парочка helper-ов, которые используются выше:
void *CreateBGRABitmap(CBitmap& Bitmap, HDC DC, CSize Size, bool UpsideDown)
{
BITMAPV5HEADER bi;
memset(&bi, 0, sizeof(bi));
bi.bV5Size = sizeof (BITMAPV5HEADER);
bi.bV5Width = Size.cx;
bi.bV5Height = UpsideDown ? Size.cy : -Size.cy;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
bi.bV5RedMask = 0x00FF0000;
bi.bV5GreenMask = 0x0000FF00;
bi.bV5BlueMask = 0x000000FF;
bi.bV5AlphaMask = 0xFF000000;
void *Bits = NULL;
Bitmap.CreateDIBSection(DC, (BITMAPINFO*)&bi, DIB_RGB_COLORS, &Bits, NULL, 0);
return Bits;
}

void ApplyAlpha(BYTE& Value, BYTE Alpha)
{
Value = ((unsigned)Value * Alpha) >> 8;
}

10 сентября 2008

Animated GIFs using GDI+

GDI+ как-то поздновато появилась. Именно такой по архитектуре нужно было делать GDI (которая без плюса) изначала. Но, так как изначально GDI сделали как сделали - получили кучу извращений для простой операции - отобразить картинку на экране.

А с GDI+ даже анимированные гифы рисовать - как два пальца. В кратце расскажу как.

Для начала абстрагируемся от GDI+, мало ли потом захотим использовать другую библиотеку. Создадим небольшой интерфейс:
class CImage
{
public:
virtual CSize Size() const = 0;
virtual void Draw(HDC DC, const CRect& DestRect, const CRect& SrcRect) const = 0;
virtual ~CImage() {}

virtual size_t GetFrameCount() const = 0;
virtual void SelectActiveFrame(size_t Frame) = 0;
virtual int /*ms*/ GetFrameDelay(size_t Frame) const = 0;
};
Создадим реализацию этого интерфейса на основе GDI+:
class CGdiPlusImage : public CImage {...}
Уже можно рисовать! Image.Draw(...) и вперед ;) Не забыть только вызвать в нужный момент SelectAcriveFrame() - и все заанимируется сразу.

Один товарисч из Объединенного Королевства предлагает создавать отдельный поток, чтобы там можно было через WaitEvent() дожидаться наступления следующего фрейма. Но зачем нам по отдельному потоку на каждую анимацию? Мы сделаем по-русски - через SetTimer(). Заведем будильник на время, когда нужно будет следующий фрейм отрисовать - и по WM_TIMER отрисуем. Сделаем небольшой helper-класс для этого:
class CAnimation
{
public:
// all time/reriods are in ms
CAnimation(CImage& Image, DWORD Time = timeGetTime());

typedef std::pair<size_t /*current frame*/, DWORD /*period till next frame*/> TInfo;
TInfo CurrentInfo(DWORD Time = timeGetTime());

size_t FrameByTime(DWORD Time = timeGetTime());
DWORD PeriodTillNextFrame(DWORD CurrentTime = timeGetTime());

private:
size_t nFrames_;
DWORD Start_;
typedef std::vector<DWORD> TFrameEnds;
TFrameEnds FrameEnds_;
};
Этот helper выдаст нам:
1) какой фрейм показывать на такой-то момент времени (чтобы в любой момент времени при отрисовке мы знали какой фрейм рисовать)
2) сколько осталось до показа следующего фрейма (чтобы знать на сколько нам будильник заводить).
Выдаст нам это либо по-отдельности, либо сразу (в виде TInfo).

Осталось реализовать получение этого самомго TInfo. Для этого в самом начале посчитаем когда какой фрейм заканчивается:
DWORD LastEnd = 0;
for (size_t nFrame = 0; nFrame < nFrames_; ++nFrame)
{
LastEnd += Image.GetFrameDelay(nFrame);
FrameEnds_[nFrame] = LastEnd;
}
тогда потом легко найдем нужный фрейм простым двоичным поиском:
DWORD FullCyclePeriod = FrameEnds_.back();
DWORD NormalizedTime = (Time - Start_) % FullCyclePeriod;
TFrameEnds::iterator Current(std::upper_bound(FrameEnds_.begin(), FrameEnds_.end(), NormalizedTime));
return TInfo(Current - FrameEnds_.begin(), *Current - NormalizedTime);
И все! Теперь при старте заводим будильник на начало второго фрейма, а при звоне будильника делаем invalidate и заводим будильник на начало следующего фрейма - и т.д. по кругу:
void OnCreate()
{
SetTimer(TimerId, Animation.PeriodTillNextFrame());
}

void OnTimer()
{
CAnimation::TInfo AnimationInfo(Animation.CurrentInfo());
Image.SelectActiveFrame(AnimationInfo.first);
SetTimer(TimerId, AnimationInfo.second);
Invalidate();
return 0;
}

Полный код примера.zip

22 августа 2008

Debugging using binary search

Бывает обидно когда вылезают баги в том месте, где раньше все работало нормально. Смотришь - баг, но точно помнишь что месяц назад этого бага не было. Причем в какой момент образовался этот баг - непонятно. Столько кода с уже добавлено и переписано.

Решение такой проблемы на корню: обвешивание всего кода юнит-тестами и прогон всех тестов после каждого коммита. Однако не всегда это реализуемо. Либо тесты писать времени нет, либо очень сложно их писать (например, для GUI).

В таком случае можно прибегнуть к двоичному поиску. У нас есть репозиторий и можно получить функцию от номера ревизии - воспроизводится ли данный баг в этой ревизии или нет. Например, в 500-ой его не было а сейчас 1030-ая и он есть. Делим отрезок ревизий [500,1030] пополам, попадаем где-то в 765-ую, апдейтимся до нее, ребилдим проект, смотрим воспроизводится ли баг. Не воспроизводится? Значит он где-то в отрезке [765,1030]. Делим его пополам, и т.д пока не найдем отрезок длинной в один коммит, например [897,898]. Значит 898-ой коммит вызвал появление бага. Смотрим этот коммит и разбираемся в чем дело, выясняем какие изменения привели к багу.

Если коммиты небольшие по объему (а я рекомендую именно так и делать), то довольно быстро можно понять где вылез баг и пофиксить его.

08 августа 2008

Typedefs

Кстати, о typedef-ах. Typedef не создает новый тип - только его псевдоним. В этом можно убедиться на простом примере:
template <typename T> class foo {};
template <> class foo<char> {};
typedef char bar;
template <> class foo<bar> {} // compile error
В последней строке получим ошибку, говорящую нам что шаблон foo для типа char уже определен.

Кстати, на счет типов. Как думаете, что выдаст следующий код:
template <typename T> struct foo { static const int x = 1; };
template <> struct foo<signed char> { static const int x = 2; };
template <> struct foo<unsigned char> { static const int x = 3; };
cout << foo<char>::x;
Единицу. Char, signed char и unsigned char - три разных типа. В зависимости от настроек компилятора char ведет себя как signed или unsigned, но это отдельный тип.

String parameters

Пытался установить Visual Studio 2005 Service Pack 1 на машине, у которой всего полтора гигабайта свободного места на диске. Думаете получилось? Нифига - места на диске не хватило!

Этот сервис пак представляет собой .exe размером 500Mb, внутри которого .msp, а внутри .msp видимо все обновляемые файлы лежат. То сначала распаковывается содержимое .exe в Temp, потом туда же распаковывается содержимое .msp, и видимо там дальше еще что-то распаковывается... Вобщем, красота.

К чему это я - к тому как часто передаются строки в качестве параметров. Строка в C++ - чаще всего std::basic_string:
void foo(const std::string& Param);
Если у нас строка хранится не в std::string, а например в const char*, то получим неявное копирование строки в конструкторе std::string.

void foo(const char* Param);
Уже лучше - можем передать string.c_str(), можем что угодно другое, где есть null terminator. А если у нас строка (или даже большой текст) загружен из ресурсов и есть const char* data() и size_t size(), но нет замыкающего нуля - снова придется делать копию. И это не единственный вариант, когда есть data()/size() - если хотим передать подстроку (внутреннюю часть какой-либо большой строки), то возникнет та же проблема.

void foo(const char* Param, size_t Size = strlen(Param));
Уже намного лучше. Правда если строка в другой кодировке или вообще, читается с потока - опять нужно складывать копию в память, а потом только вызывать foo().

А хорошая альтернатива - это парочка итераторов (a-la STL) или контейнер (a-la Boost):
template <typename T>> void foo(T First, T Last);
template <typename T>> void foo(const &T String);

Конструкции почти эквивалентны, т.к. у контейнера легко можно взять String.begin() и String.end(), а пару итераторов - скормить boost::make_iterator_range(). Так что скорее вопрос вкуса, какой вариант выбрать - я за контейнер.

С таким интерфейсом мы можем взять свести копирование к минимуму. Например, пришли данные в urlencode, да еще в win1251:
char* data = "q=%EF%F0%E8%E2%E5%F2&a=%EC%E5%E4%E2%E5%E4";

Парсим все в указатели:
typedef std::pair<char*, char*> substr;
typedef std::pair<substr, substr> item;


Делаем итератор для декодирования urldecode ("%EF%F0%E8%E2%E5%F2" -> "привет") - и его operator* и operator++ будут делать всю работу "на ходу". (Тут можно призвать на помощь boost::iterator_facade). Нужно конвертировать в utf-8? - еще один итератор ;) И все это скормим нашей foo().

PS. Подробности такого подхода - в boost.iterator, boost.range, boost.string algo.

05 августа 2008

Easy double-buffering

Типичный код отрисовки окна:
LRESULT CMyWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CRect ClientRect;
GetClientRect(ClientRect);
WTL::CPaintDC DC(m_hWnd);

DrawBackground(DC, ClientRect);
DrawContent(DC, ClientRect);
...
}
Если все это перерисовывается довольно часто, то возникает мерцание. Например при изменении мышкой размера окна — на каждое движение мыши идет перерисовка, сначала рисуется фон, потом контент, легко застать момент когда фон отрисовался, а остальное - нет.

Типичный паттерн чтобы этого избежать — рисовать во время обратного хода луча использовать double buffering. Думаете, это сложно? На самом деле — как два пальца. Понадобится всего лишь небольшой вспомогательный класс:
LRESULT CMyWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CRect ClientRect;
GetClientRect(ClientRect);
WTL::CPaintDC PaintDC(m_hWnd);
CMemDC DC(PaintDC, ClientRect);

DrawBackground(DC, ClientRect);
DrawContent(DC, ClientRect);
...
}

Сам "помощник" выглядит так:
class CMemDC : public WTL::CDC
{
public:
CMemDC(HDC PaintDC, const RECT &Area) : _PaintDC(PaintDC), _Area(Area)
{
CreateCompatibleDC(PaintDC);

POINT Viewport;
::GetViewportOrgEx(PaintDC, &Viewport);
SetViewportOrg(Viewport);

_Bitmap.CreateCompatibleBitmap(PaintDC, _Area.Width(), _Area.Height());
SelectBitmap(_Bitmap);
};

~CMemDC()
{
POINT OriginalViewport;
SetViewportOrg(0, 0);
_PaintDC.SetViewportOrg(0, 0, &OriginalViewport);
_PaintDC.BitBlt(_Area.left, _Area.top, _Area.Width(), _Area.Height(), m_hDC, 0, 0, SRCCOPY);
_PaintDC.SetViewportOrg(OriginalViewport.x, OriginalViewport.y);
}

private:
WTL::CBitmap _Bitmap;
WTL::CDCHandle _PaintDC;
CRect _Area;
};

25 июля 2008

Microsoft's APIs: features, not bugs

У Microsoft если что-то работает не так, как ожидалось - это не баги, а фичи. Они документируются и их не чинят.

Мне (и другим тоже) почему-то казалось, что функция AplhaBlend() должна брать обычное сырое (raw) RGBA изображение и рисовать его с учетом прозрачности. И я долго не мог понять, почему точки с A=0 и с A=255 рисуются нормально, а все остальное - неправильно (получается очень белое).

Оказывается, нужно заранее самостоятельно наложить альфа-канал на картинку, типа такого:
bgra_pixel *It(Bits);
for (int Left = Width * Height; Left; --Left, ++It)
{
It->blue = ((unsigned)It->blue * It->alpha) >> 8;
It->green = ((unsigned)It->green * It->alpha) >> 8;
It->red = ((unsigned)It->red * It->alpha) >> 8;
}

19 июня 2008

Boost.Range for null-terminated strings

Оказывается то, что boost::begin()/end()/size() перестали работать с char*/wchar_t* - это фича, а не баг. Все дело оказалось вот в чем:
const char nullterminated[] = "hi"; // sizeof(nullterminated) == 3
const char[2] array = { "h", "i" }; // sizeof(array) == 2
boost::size(nullterminated) == boost::size(array) ?;
В таком случае boost::size() не может догадаться есть ли у массива null terminator или нет, поэтому решили отдать этот вопрос "пользователям", введя as_literal/as_array.

То есть теперь только так: boost::size(boost::as_literal("hello"));

18 июня 2008

Boost 1.35 issues

Boost 1.35 меня убивает. То, что при компиляции моих проектов появились варнинги там где их раньше не было - это меньшее из зол. Тут столкнулся со следующим: Boost.Range перестало работать с null terminated strings.

Вот такой вот код компилируется и работает с 1.34.1 и не компилируется 1.35:
const char* String = "hello";
size_t Len = boost::end(String) - boost::begin(String);

PS. Для меня было откровением узнать, что в NTFS есть hard- и symbolic links. Можно создать папочки boost_1_35 и boost_1_34_1 и символическую ссылку boost на одну из них. При необходимости можно легко поменять ссылку.

17 июня 2008

Referrer in Internet Explorer

Оказывается, Internet Explorer не передает referrer-а при открытии страниц в новом окне или вкладке. На открытой странице в javascript-е можно узнать referrer-а через window.opener.location. Однако, если попытаться получить window.opener через IHTMLWindow2::opener, то получим фиг нулевой указатель.

Попробовал через IHTMLWindow2::execScript выполнить SomeTempVar = window.opener.location.href (execScript не возвращает значения, приходится извращаться через временную переменную) - та же фигня.

Пришлось ловить NewWindow3 (а оно требует минимум IE6 в WinXP SP2), запоминать кто кого открыл, а потом "вспоминать" в другом экземпляре WebBrowser-а.

Поражаюсь разработчикам Internet Explorer-а.

22 мая 2008

Smart pointer for COM objects

Указатели на объекты должны быть умными - чтобы автоматически управлять временем жизни объекта. Это понятное дело. Для COM объектов есть широкий выбор стандартных умных указателей: _com_ptr_t, CComPtr, CComQIPtr. Какой же из них предпочесть? Большой разницы между ними нет, однако вся соль - в нюансах.

Шаблоны CComPtr и CComQIPtr предлагает нам ATL. Так что ести вы не используете ATL у себя в проекте, то ваш выбор однозначен - _com_ptr_t. Тут даже думать не надо - особого смысла пораждать зависимость от ATL ради CComPtr/CComQIPtr нет.

У _com_ptr_t есть полезный макрос для создания конкретных типов (через typedef): _COM_SMARTPTR_TYPEDEF. Выглядит это так:
// = typedef _com_ptr_t<...> IFooBarPtr;
_COM_SMARTPTR_TYPEDEF(IFooBar, __uuidof(IFooBar));
...
IFooBarPtr FooBar;
Еще бОльшая прелесть состоит в том, что для большинства интерфейсов уже заданы такие typedef-ы, то есть можно смело писать IHTMLDocument2Ptr Document; без соответствующего тайпдефа. Однако засада в том, что сделаны не все тайпдефы и для некоторых интерфейсов придется либо самим объявлять тип через _COM_SMARTPTR_TYPEDEF или использовать _com_ptr_t напрямую.

_com_ptr_t, как и CComQIPtr умеют вызывать QueryInterface "на ходу", что довольно удобно:
IDispatchPtr RawDocument = GetDocument();
IHTMLDocument2Ptr Document(RawDocument); // здесь автоматически вызывается QueryInterface
Небольшим плюсом CComQIPtr может служить его бОльшая лаконичность по сравнению с _com_ptr_t:

CComQIPtr<IHTMLDocument4> Document;
vs
_com_ptr_t<IHTMLDocument4, &__uuidof(IHTMLDocument4)> Document;

Вывод из этого я делаю такой: если используете ATL — берите CComQIPtr, если нет — _com_ptr_t. Если вдруг придется "слезть" с ATL, то будет довольно просто перевести код с CComQIPtr на _com_ptr_t.

Post scriptum
Все перечисленные выше умные указатели переопределяют operator& так, что он возвращает адрес обычного указателя, вместо адреса умного указателя. Это очень удобно для таких вот вызовов, которых в COM предостаточно:
IWebBrowserPtr Browser = GetBrowser();
IDispatchPtr Document;
// calling HRESULT STDMETHODCALLTYPE get_Document(IDispatch **ppDisp);
Browser->get_Document(&Document);
Соответственно, если будет желание положить элемент в контейнер, то оператору & очень желательно вернуть прежнюю логику. Например, обернув умный указатель в CAdapt из ATL:
std::vector< CAdapt< CComQIPtr<IDispatch> > > Objects
А если понадобится, то можно в любой момент сделать "срезку" до CComQIPtr/_com_ptr_t с его переопределенным оператором &:
BOOST_FOREACH(CComQIPtr<IDispatch> &Object, Objects)
Browser->get_Document(&Object);

PPS. Дискуссии по теме: здесь и здесь.

20 мая 2008

Naming arguments in constructors

Кстати, имена аргументов в конструкторе вполне могут совпадать с именами членов:
class foo
{
foo(int x, int y) : x(x), y(y) {}
int x, y;
};

16 мая 2008

ActiveX controls registration issues

ATL дает довольно удобные средства для создания ActiveX control-ов: стандартный визард вообще весь необходимый код генерирует сам. С проблемами можно столкнуться только при регистрации контролов из под кода, выполняющегося без администраторских прав.

Что можно предпринять в этом случае? На этот случай есть хороший выход - per user component registration: если регистрироваться не в HKEY_CLASSES_ROOT (который на запись мапится в HKEY_LOCAL_MACHINE\SOFTWARE\Classes), а в HKEY_CURRENT_USER\Software\Classes. для этого можно:
а) поправить .rgs файлы, сгенерированные визардом,
б) использовать перенаправление реестра.
Второй вариант хорош тем, что можно это делать по условию "запущены ли мы под админскими правами":
if (!IsAdmin())
{
HKEY hKeyCurrentUser;
RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Classes"), 0, KEY_ALL_ACCESS, &hKeyCurrentUser);
RegOverridePredefKey(HKEY_CLASSES_ROOT, hKeyCurrentUser);
RegCloseKey(hKeyCurrentUser);
}
Облом состоит в том, что в регистрации библиотек типов под Вистой есть ошибка, которая не схватывает перенаправление реестра. Предлагается поставить Service Pack и вызвать хитрую функцию из хитрой библиотеки. Но это не наш путь, требовать всяких сервис паков. В результате регистрацию библиотек типов нужно делать вручную (точнее полу-автоматом - написав .rgs файл), отключив при этом стандартную регистрацию параметром FALSE в выховах _AtlModule.DllRegisterServer() и _AtlModule.DllUnregisterServer().

Но все эти труды будут напрасны, если ваш ActiveX control устанавливается Internet Explorer-ом под Вистой. Он устанавливает компоненты только под админскими правами, то есть если у пользователя их нет - вылезет окно UACа :(

26 апреля 2008

Speeding up compilation

Компиляция C++ проектов - вещь довольно затратная по времени. Что можно предпринять для ускорения? Мои советы такие:

1. Разбивайте проект на отдельно компилируемые файлы - тогда при изменении одного фрагмента системы перекомпилируется только этот файл. Тут, правда, палка о двух концах - возрастет время rebuild-а, но полный ребилд делается обычно реже чем фазы "немного поправил - скомпилировал".

2. Включайте (#include) только самое необходимое. Иногда достаточно включить some_forward.h или some_feature1.h вместо большого some.h. Для использовании ссылки или указателя на класс компилятору не нужно знать все о классе - пользуйтесь этим при написании header-файлов:
//вместо #include "SomeClass.h"
class SomeClass;

void Foo(SomeClass&);
void Bar(SomeClass*);
А шаблоны вообще раскрываются в момент использования, а не декларации.

3. Распараллеливайте компиляцию на несколько процессоров.

4. Тривиально: поставьте побольше оперативной памяти.

5. Во время компиляции запустите system monitor и убедитесь, что не только процессор и память имеют значение. Узкое место: жесткий диск. Поставьте два три четыре быстрых диска в RAID0. Я серьезно.

20 апреля 2008

Linkdumps from DOU

На developers.com.ua еженедельно раздают замечательные подборки ссылок. Можно подписаться через общий rss, а можно отфильтровать по слову "linkdump".

06 апреля 2008

Accessing old-school APIs as C++ containers

Часто API и библиотеки предоставляют доступ к набору объектов в следующем виде:
size_t GetItemCount();
value GetItem(size_t Index);
Особенно это относится к библиотекам на C. Оказалось, очень просто преобразовать такой интерфейс к более стандартному для C++ виду - итераторам:
class iterator : public boost::iterator_facade<iterator, value,
std::random_access_iterator_tag, value, size_t>
{
public:
iterator(size_t Index) : Index_(Index) { }

// implementation
public:
reference dereference() const { return GetItem(Index_); }
void increment() { ++Index_; }
void decrement() { --Index_; }
bool equal(const iterator& It) const { return Index_ == It.Index_; }

private:
size_t Index_;
};
Теперь можно делать STL-way:
std::copy(iterator(0), iterator(GetItemCount()), ...);
Или даже так:
BOOST_FOREACH(value v, make_iterator_range(iterator(0), iterator(GetItemCount())))
...;

30 марта 2008

Boost 1.35

Вышел очередной Boost. Много нового. Собирать с опцией --build-type=complete.

22 февраля 2008

Coding Standards

По ссылке с DOU: coding standards одного проекта. 141 страница! Очень грамотно что помимо самого правила описывают и причину его введения.

18 февраля 2008

Passing values to a function

"Стандартный" способ передать N значений типа T на C:
void foo(T* values, int N);
Стандартное решение для C++:
template <typename it_t>
void foo(it_t first, it_t last);
Ну, если у нас есть vector<string> и foo() работает с string - то все тривиально. Засада если у нас все лежит как-нибудь не так, например в map<int,string> или в каком-нибудь vector<Object>, у которого нужно вызвать Object::GetString() для каждого элемента. А если еще и не все значения подряд нужны?

Можно, конечно, в цикле перегнать нужные значения в отдельный контейнер, и уже дальше передать его. Но мало того, что это не путь настоящих джедаев C++-программистов, так еще и кучу процессорного времени потеряем да и памяти заодно.

Тут стоит вспомнить, что умные дядьки уже подумали о нас, написали нужные адаптеры для итераторов, и запихнули все это в boost. Так, transform iterator достанет нам строку из pair<int,string> или Object::GetString(), а filter iterator отфильтрует нам элементы на ходу.

14 февраля 2008

std::equal_to

С удивлением обнаружил, что std::equal_to и сотоварищи объявлены так:
template<class _Ty>
struct equal_to
: public binary_function<_Ty, _Ty, bool>
{ // functor for operator==
bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator== to operands
return (_Left == _Right);
}
};

Я-то наивно полагал, что должно быть как-то так:
template<class T1, class T2 = T1>
struct EqualTo : public std::binary_function<T1, T2, bool>
{
bool operator()(typename boost::call_traits<T1>::param_type Left,
typename boost::call_traits<T2>::param_type Right) const
{
return Left == Right;
}
};

08 февраля 2008

Multithreading: Messages

Хорошие языки учат хорошим манерам, но это не мешает использовать эти манеры не только в этих самых хороших языках. (Умные указатели тому пример.)

Многопоточность в C++ - довольно нетривиальная вещь, так как сам язык не дает (пока) абсолютно ничего для этого. Типичная подход - защита совместно используемых объектов критическими секциям. При неаккуратном обращении с ними недалеко до простоя потоков, а иногда и до deadlock-ов.

Как в свое время все вызовы new/delete упаковали в умные указатели, можно так же поступить и с критическими секциями, оставив для общения потоков такой вот простой интерфейс:
template <typename T>
void SendMessage(Thread, std::auto_ptr<T> Message);
(можно отказаться от шаблона в пользу IMessage вместо T - это на свой вкус и цвет).

Послали сообщение - и бежим дальше, никаких лишних простоев.

PS. Почему auto_ptr, а не копия объекта: не всегда нужна копия, часто сообщение готовится только для того чтобы быть посланным - нет смысла делать оверхед в виде копии. А уж если нужна копия, то все просто:
SendMessage(SomeThread, Сlone(Message));
(или Message.Сlone(), у кого как)

05 февраля 2008

Quick way or right way

Бывало ли у вас так, что можно сделать:
а) как проще
б) как правильно?

У меня - постоянно ;) Примеров - хоть отбавляй. Одна из наиболее частых ситуаций: добавление нового атрибута (member-а) к объекту.

Как это выглядит:
class Object { ... };

class ObjectUser1
{
void UseObject(Object&);
Object ReturnObject();
std::container<Object> SomeMember_;
};

class ObjectUser2...

И тут для конкретной задачи UserOfObjectUser1 требуется чтобы Object еще имел, например, и атрибут Name:
class Object
{
... здесь то, что было...

// добавляем
void SetName(NameType);
NameType Name() const;
NameType Name_;
};
Хочется думать, что Name - очень полезный атрибут для Object, и он ему совсем не помешает. Хотя предыдущим ObjectUser-ам (и user-ам этих user-ов) он вообще не нужен, но они при новой компиляции ничего и не заметят. А думать так хочется потому как альтернатива этому - такова:
class ObjectWithName : public Object
{
ObjectWithName(A a) : Object(a) {}
ObjectWithName(B b, C c) : Object(b, c) {}
ObjectWithName(D d) : Object(d) {}
...
... сколько еще там у Object конструкторов?
... и не забыть добавить сюда новый конструктор если появится у Object
...
ObjectWithName(const Object& o) : Object(o) {}

void SetName(NameType);
NameType Name() const;
NameType Name_;
};
Да еще ObjectUser1 придется делать шаблонным - чтобы мог работать как с Object, так и с ObjectWithName.

Но если сделать как проще, а не как правильно - последствия дадут знать о себе потом. Вот пример:
class File
{
string Name;
int Size;
vector DiskSectors;
void Read(...);
void Write(...);
}
Хотя файл по определению - именованная область на диске, но Name явно здесь лишнее. Точнее - лишнее все, кроме Name. Не верите? Попробуйте создать hard link ;)

Истина где-то рядом:
class DiskArea
{
int Size;
vector<int> DiskSectors;
void Read(...);
void Write(...);
};

class File
{
string Name;
shared_ptr<DiskArea> Area;

// адаптеры - расплата за правильный дизайн
void Read(...) { Area->Read(...); }
void Write(...) { Area->Write(...); }
};

09 января 2008

Function overloading vs template specialization

Перегрузка функций работает отлично со времен plain C. Пример:

Foo.h:
void Foo(bool);
void Foo(int);
void Foo(const char*);

Foo.cpp:
void Foo(bool) { ... }
void Foo(int) { ... }
void Foo(const char*) { ... }

Test.cpp:
#include "Foo.h"

void Test()
{
bool b(true);
int i(10);
const char *c("hello");

Foo(b);
Foo(i);
Foo(c);

Foo(*c); // хм... компилируется!

shared_ptr<int> p;
Foo(p); // тоже компилируется
}

Неприятность доставляет тип bool, к которому конвертируются многие другие типы. Например, можно забыть разыменовать [умный] указатель, и компилятор нам ничего не подскажет.

Все было бы замечательно, если бы можно было написать void Foo(explicit bool);, но ведь в случае параметра функции explicit не прокатит...

Выход - вместо перегрузки функций использовать специализацию шаблонов. Правим Foo.h:
template <typename T> void Foo(T);
template <> void Foo(bool);
template <> void Foo(int);
template <> void Foo(const char*);

Кстати, у меня под Visual C++ 2005 не пришлось даже менять и/или перекомпилировать Foo.cpp - сингатуры функций в объектных файлах для template <> void Foo(xxx) и void Foo(xxx) видимо совпадают.

Теперь Foo(*c) и Foo(p) из приведенного выше примера не пройдут - линкер скажет, что не нашел определений нужных символов. Можно попросить ругаться не линкер, а компилятор, добавив немного кода в первую строчку Foo.h:
template <typename T> void Foo(T) { typename T::IncorrectParameterType; }


Из минусов такого подхода - все вызовы Foo() требуют теперь точного указания типа параметра:
class ConvertableToInt
{
public:
operator int() { return 0; }
};
Foo(ConvertableToInt()) // теперь так не получится :(
Foo(static_cast<int>(ConvertableToInt())) // только так;

Если такие минусы не устраивают - придется идти на более радикальные меры, например разделить реализации bool и не-bool на функции с разными именами. Интерфейс при этом останется тот же - Foo(x):

Новый Foo.h:
template <typename T> void Foo(T t) { FooImpl(t); }
void Foo(bool);
void FooImpl(int);
void FooImpl(const char*);

Новый Foo.cpp:
void Foo(bool) { ... }
void FooImpl(int) { ... }
void FooImpl(const char*) { ... }