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(...); }
};