Friday, February 16, 2007

 
Головоловка
Синтаксис языка С++ иногда просто поражает.  
Как Вы думаете что означает это объявление:

char (*(*(*C::foo(int(C::*)(int const) const) const))())[][1];

Wednesday, November 08, 2006

 
Comeau всегда прав!...?
Код, который я пишу на работе, должен собираться сразу несколькими компиляторами. Само собой иногда возникают разногласия в их трактовках стандарта языка. Обычно, при неоднозначном результате, я считаю правым gcc, однако, в особо щекотливых ситуациях, проверяю код on-line вариантом Comeau. Лишь затем «курю» стандарт, уточняя оставшиеся непонятными моменты. Comeau всегда дает мне 90% понимания сутации.
[Дальше...]

Tuesday, October 17, 2006

 
Баг VS или как получить доступ к приватному интерфейсу
Случайно столкнулся c интересным багом в VS2003/VS2005.
Нехитрый прием позволяет получить доступ к приватному интерфейсу наследуемого класса.

struct ifoo
{
void test() {}
};

class foo : private ifoo
{
};

struct delegate
{
delegate(ifoo* i) { i->test(); }
};

class bar : private foo
{
public:
bar() { delegate(this); } // sic! вызов ifoo::test
};

Эмпирические исследования показали, что прием работает только если ifoo является структурой и delegate используется как временный объект.

Не устаю предупреждать:
Beware of bugs

Saturday, August 26, 2006

 
Отпуск
Впереди долгожданный отпуск! Улетаю на две недели к черному морю.
Обещаю по возвращению порадовать читателей новыми интересными постами.

 
ADL в деле
Продолжение предыдущего поста "Особенности поиска имен".

Как заставить компилятор при поиске operator<< заглянуть в наше собственное пространство имен?
В предыдущем посте я упомянул про ADL и его особенности:
"...ADL предполагает поиск имен в пространствах имен типов агрументов анализируемого выражения..."

Этот вариант поиска имен нам и поможет.
Вызов operator<< находится в operator= класса std::ostream_iterator, а типами аргументов этого выражения являются std::ostream и тип передаваемый std::ostream_iterator в качестве первого параметра шаблона. Мы сами явно его задаем, создавая конкретный экземпляр класса:

std::ostream_iterator< std::pair<std::string, int> >(std::cout)

Заменив std::pair<std::string, int> на некоторый тип (назовен его wrapper) из того же пространства имен что и operator<<, мы заставим компилятор произвести поиск в этом пространстве имен. Следовательно, наш вариант operator<< будет найдет.

Отлично. Проблема поиска решена. Но появились две другие, к счастью легко решаемые проблемы.
Первая заключается в том, что в определении operator= класса std::ostream_iterator требуется теперь в качестве аргумента объект типа wrapper const&, а передается ему std::pair<const std::string, int>.
Вторая проблема обратна первой. Наш operator<< требует в качестве второго аргумента std::pair<const std::string, int> const&, а передается ему wrapper.

Итого, объект типа wrapper должен уметь неявно конструироваться из std::pair<const std::string, int> (проблема 1), неявно преобразовываться к ссылке на константу этого типа (проблема 2), а также хранить саму ссылку.
Обобщая, сам тип можно сделать шаблонным:

template <class T>
class wrapper
{
T& t_; // хранимая ссылка
public:
wrapper(T& t) : t_(t) {} // неявный конструктор из T
operator T&() const { return t_; } // неявное преобразование к T&
};

Теперь wrapper можно опробовать в деле. Поместим объявление этого типа в пространство имен foo, туда же поместим и operator<<. В качестве аргумента шаблона класса std::ostream_iterator укажем wrapper с std::pair<const std::string, int> в качестве T.
Весь пример:

#include <map>
#include <string>
#include <iostream>

namespace foo
{

template <class T>
class wrapper
{
T& t_;
public:
wrapper(T& t) : t_(t) {}
operator T&() const { return t_; }
};

std::ostream& operator<< (std::ostream& str,
std::pair<const std::string, int> const& val)
{
str << val.first << " = " << val.second << std::endl;
return str;
}

} // namespace foo

int main()
{
std::multimap<std::string, int> int_map;
int_map.insert(std::make_pair("foo", 1));
int_map.insert(std::make_pair("foo", 2));

typedef foo::wrapper< std::pair<const std::string, int> > pair_wrapper;
std::copy(int_map.begin(), int_map.end(),
std::ostream_iterator<pair_wrapper>(std::cout));
}

Замечательно, все работает так, как и требовалось, а operator<< вынесен из пространства имен std!

Остается только уточнить небольшой ньюанс. Может появиться желание сделать wrapper еще более универсальным вынеся его в некоторое другое пространство имен, например common, для удобстра использования в случае повторного применения. В foo же при помощи typedef объявить псевдоним wrapper, инстанцируя его для конкретного типа.

Делать этого нельзя, поскольку поиск будет проведен только в пространстве имен в котором объявлен wrapper. Пространства имен его псевдонимов при поиске имен рассмотрены не будут.

Thursday, August 24, 2006

 
Особенности поиска имен
В предыдущем посте "Контейнеры с эквивалентным ключем" я упростил пример вывода на экран элементов multimap, добавив перегрузку operator<< в пространство имен std. Тем самым несколько покривил душой, ибо стандарт явно запрещает подобные эксперименты (см. 17.4.3.1).

Давайте еще раз взглянем на пример и выясним первопричину вынудившую меня так поступить.
Ключевые моменты примера из предыдушего поста:

namespace std
{

std::ostream& operator<<(std::ostream& str,
std::pair<std::string, int> const& val);

} // namespace std

int main()
{
// ...
std::copy(int_map.begin(), int_map.end(),
std::ostream_iterator<
std::pair<std::string, int> >(std::cout));
}

Как компилятор найдет перегрузку operator<< из примера? Ключевое слово - поиск имен: Ordinary Lookup (OL) и Argument-Dependent Lookup (ADL).

Согласно OL, встретив неквалифицированное имя, компилятор произведет поиск в той области видимости,в которой оно ему встретилось. В нашем случае это области видимости класса (class-scope) ostream_iterator, в operator= которого и вызывается перегружаемый нами operator<<, и пространство имен std, поскольку именно в нем находится объявление ostream_iterator.

Если компилятор не найдет подходящего operator<< согласно OL, поиск продолжится с использованием ADL. Несколько упрощая скажу, что ADL предполагает поиск имен в пространствах имен типов агрументов анализируемого выражения. Типами аргументами при вызове operator<< являются std::ostream и std::pair<>.
Другими словами поиск подходящего operator<< будет снова произведен в пространстве имен std.

Подитожу. Компилятор сможет найти перегрузку operator<< в области видимости класса ostream_iterator и пространстве имен std. Поскольку определение класса ostream_iterator мы изменить не в состоянии, остается только добавить перегрузку в std. Что я и сделал в примере.

В следующем посте я расскажу о том, как избежать внесения operator<< в пространство имен std.

Monday, August 14, 2006

 
Контейнеры с эквивалентным ключом
Стоит помнить, что стандарт не дает гарантии перебора элементов с эквивалентным ключем в порядке их вставки в контейнер (multimap, multiset).
Т.е. тот порядок, в котором следующий код выведет на экран список элементов в multimap не определен, и зависит от вендора:

#include <map>
#include <string>
#include <iostream>

namespace std
{

std::ostream& operator<<(std::ostream& str,
std::pair<std::string, int> const& val)
{
str << val.first << " = " << val.second << std::endl;
return str;
}

} // namespace std

int main()
{
std::multimap<std::string, int> int_map;
int_map.insert(std::make_pair("foo", 1));
int_map.insert(std::make_pair("foo", 2));

std::copy(int_map.begin(), int_map.end(),
std::ostream_iterator<
std::pair<std::string, int> >(std::cout));
}
Некоторые вендоры дают подобную гарантию, тогда закладка на порядок перебора делает Ваш код хотя и корректным, но непереносимым.
К примеру код скомпилированный VS2005 c библиотекой STL по умолчанию, выводит на экран элементы в порядке их вставки к контейнер:

foo = 1
foo = 2

Повторю еще раз, не стоит на это закладываться. Стандарт не гарантирует порядка вывода элеметнов в такой последовательности.

Thursday, August 10, 2006

 
Указатель на функцию-член класса в качестве параметра шаблона класса
  В языке C++ cуществуют три вида параметров шаблонов (не путать с агрументами шаблонов, т.е. значениями, которые подставляются вместо параметров шаблона при инстанцировании шаблона).
  1. Параметры типа
  2. Параметры не являющиеся типами
  3. Шаблонные параметры шаблонов
  Сегодня я бы хотел остановиться подробнее на параметрах не являющихся типами.
Не так часто используемые, как параметры типа, они, тем не менее, позволяют создавать интересные конструкции.
  Не являющиеся типами параметры - это константные значения, которые могут быть определены при компиляции или при компоновке.

Одним из типов такого параметра (т.е. типов значений, которые они обозначают) может быть тип указателя на функцию-член класса, который и заслужил отдельной темы.

Следующий пример демонстрирует технику использования указателя на функцию-член класса в качестве параметров шаблонов класса. Я поочередно приведу код каждого класса и функции примера:

Mutex - класс, представляющий некоторый объект межпроцессной синхронизации Mutex.
Его функции-члены позволяют захватить mutex (Lock) и освободить его (Unlock).

struct Mutex
{
  void Lock() {}
  void Unlock() {}
};

Locker - класс автоматического управления Mutex.
В конструкторе объект Mutex захватывается, а в деструкторе - освобождается.

struct Locker
{
  Locker(Mutex& m) : m_(m) { m_.Lock(); }
  ~Locker() { m_.Unlock(); }
  Mutex& m_;
};

Unlocker - класс автоматического управления Mutex обратный Locker.
В конструкторе объект Mutex освобождается, а в деструкторе - захватывается.

struct Unlocker
{
  Unlocker(Mutex& m) : m_(m) { m_.Unlock(); }
  ~Unlocker() { m_.Lock(); }
  Mutex& m_;
};

Использование симбиоза Mutex и Locker тривиально:

void foo(Mutex& m)
{
  Locker locker(m);
  // безопасный код;
}

А теперь вглядитесь в классы Locker и Unlocker. Их код не кажется вам похожим?
Если ли возможность обобщить код в некоторый шаблон класса и тем самым избежать дублирования?
Ответ - да. Воспользуемся указателями на функцию-член класса в качестве шаблона класса
Вот, чтополучается в итоге:

LockerT - шаблон класса, вызывающий в конструкторе функцию по адресу Do для переданного объекта типа Т, и вызывающий в деструкторе функцию по адресу Undo для этого же объекта.
void(T::*)(void) - тип функции-члена класса T, без параметров и возвращающая void.
Обратите внимание на дополнительные скобки при вызове функции-члена класса по указателю. Таков синтаксис подбных вызовов в языке.

template <class T, void(T::*Do)(void), void(T::*Undo)(void)> struct LockerT
{
  LockerT(T& m) : m_(m) { ((m_).*Do)(); }
  ~LockerT() { ((m_).*Undo)(); }
  T& m_;
};

Осталось только объявить типы локеров с конкретными аргументами.

typedef LockerT<Mutex, &Mutex::Unlock, &Mutex::Lock> Locker;
typedef LockerT<Mutex, &Mutex::Lock, &Mutex::Unlock> Unlocker;

И можно использовать как раньше:

void foo(Mutex& m)
{
  Locker locker(m);
  // безопасный код;
}

Помимо того, что получилось избежать дублирования кода, упрощается процесс создания локеров для других объектов синхронизации (и не только).

CriticalSection - класс, представляющий некоторый объект межпотоковой синхронизации
Его функции-члены позволяют войти в критическую секцию (Enter) и выйти из нее (Leave).

struct CriticalSection
{
  void Enter() {}
  void Leave() {}
};

Объявить локера для CriticalSection с помощью LockerT так же просто.

typedef LockerT<CriticalSection, &CriticalSection::Enter, &CriticalSection::Exit> CSLocker;

 
"The Most Important C++ Books...Ever" by Scott Meyers
Статья Скота Мейерса The Most Important C++ Books...Ever.
В ней он приводит свою версию 5-ти наиболее важных книг по C++.
По мнению Мейерса это:

  1. The C++ Programming Language by Bjarne Stroustrup
  2. Effective C++ by Scott Meyers
  3. Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
  4. International Standard for C++
  5. Modern C++ Design by Andrei Alexandrescu
Интересно, что за бортом остался Саттер

This page is powered by Blogger. Isn't yours?