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.
Нехитрый прием позволяет получить доступ к приватному интерфейсу наследуемого класса.
Эмпирические исследования показали, что прием работает только если ifoo является структурой и delegate используется как временный объект.
Не устаю предупреждать:
Beware of bugs
Нехитрый прием позволяет получить доступ к приватному интерфейсу наследуемого класса.
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::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), а также хранить саму ссылку.
Обобщая, сам тип можно сделать шаблонным:
Теперь wrapper можно опробовать в деле. Поместим объявление этого типа в пространство имен foo, туда же поместим и operator<<. В качестве аргумента шаблона класса std::ostream_iterator укажем wrapper с std::pair<const std::string, int> в качестве T.
Весь пример:
Замечательно, все работает так, как и требовалось, а operator<< вынесен из пространства имен std!
Остается только уточнить небольшой ньюанс. Может появиться желание сделать wrapper еще более универсальным вынеся его в некоторое другое пространство имен, например common, для удобстра использования в случае повторного применения. В foo же при помощи typedef объявить псевдоним wrapper, инстанцируя его для конкретного типа.
Делать этого нельзя, поскольку поиск будет проведен только в пространстве имен в котором объявлен wrapper. Пространства имен его псевдонимов при поиске имен рассмотрены не будут.
Как заставить компилятор при поиске 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).
Давайте еще раз взглянем на пример и выясним первопричину вынудившую меня так поступить.
Ключевые моменты примера из предыдушего поста:
Как компилятор найдет перегрузку 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.
Давайте еще раз взглянем на пример и выясним первопричину вынудившую меня так поступить.
Ключевые моменты примера из предыдушего поста:
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 не определен, и зависит от вендора:
Некоторые вендоры дают подобную гарантию, тогда закладка на порядок перебора делает Ваш код хотя и корректным, но непереносимым.
К примеру код скомпилированный VS2005 c библиотекой STL по умолчанию, выводит на экран элементы в порядке их вставки к контейнер:
Повторю еще раз, не стоит на это закладываться. Стандарт не гарантирует порядка вывода элеметнов в такой последовательности.
Т.е. тот порядок, в котором следующий код выведет на экран список элементов в 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уществуют три вида параметров шаблонов (не путать с агрументами шаблонов, т.е. значениями, которые подставляются вместо параметров шаблона при инстанцировании шаблона).
Не так часто используемые, как параметры типа, они, тем не менее, позволяют создавать интересные конструкции.
Не являющиеся типами параметры - это константные значения, которые могут быть определены при компиляции или при компоновке.
Одним из типов такого параметра (т.е. типов значений, которые они обозначают) может быть тип указателя на функцию-член класса, который и заслужил отдельной темы.
Следующий пример демонстрирует технику использования указателя на функцию-член класса в качестве параметров шаблонов класса. Я поочередно приведу код каждого класса и функции примера:
Mutex - класс, представляющий некоторый объект межпроцессной синхронизации Mutex.
Его функции-члены позволяют захватить mutex (Lock) и освободить его (Unlock).
Locker - класс автоматического управления Mutex.
В конструкторе объект Mutex захватывается, а в деструкторе - освобождается.
Unlocker - класс автоматического управления Mutex обратный Locker.
В конструкторе объект Mutex освобождается, а в деструкторе - захватывается.
Использование симбиоза Mutex и Locker тривиально:
А теперь вглядитесь в классы Locker и Unlocker. Их код не кажется вам похожим?
Если ли возможность обобщить код в некоторый шаблон класса и тем самым избежать дублирования?
Ответ - да. Воспользуемся указателями на функцию-член класса в качестве шаблона класса
Вот, чтополучается в итоге:
LockerT - шаблон класса, вызывающий в конструкторе функцию по адресу Do для переданного объекта типа Т, и вызывающий в деструкторе функцию по адресу Undo для этого же объекта.
void(T::*)(void) - тип функции-члена класса T, без параметров и возвращающая void.
Обратите внимание на дополнительные скобки при вызове функции-члена класса по указателю. Таков синтаксис подбных вызовов в языке.
Осталось только объявить типы локеров с конкретными аргументами.
И можно использовать как раньше:
Помимо того, что получилось избежать дублирования кода, упрощается процесс создания локеров для других объектов синхронизации (и не только).
CriticalSection - класс, представляющий некоторый объект межпотоковой синхронизации
Его функции-члены позволяют войти в критическую секцию (Enter) и выйти из нее (Leave).
Объявить локера для CriticalSection с помощью LockerT так же просто.
- Параметры типа
- Параметры не являющиеся типами
- Шаблонные параметры шаблонов
Не так часто используемые, как параметры типа, они, тем не менее, позволяют создавать интересные конструкции.
Не являющиеся типами параметры - это константные значения, которые могут быть определены при компиляции или при компоновке.
Одним из типов такого параметра (т.е. типов значений, которые они обозначают) может быть тип указателя на функцию-член класса, который и заслужил отдельной темы.
Следующий пример демонстрирует технику использования указателя на функцию-член класса в качестве параметров шаблонов класса. Я поочередно приведу код каждого класса и функции примера:
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++.
По мнению Мейерса это:
В ней он приводит свою версию 5-ти наиболее важных книг по C++.
По мнению Мейерса это:
- The C++ Programming Language by Bjarne Stroustrup
- Effective C++ by Scott Meyers
- Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
- International Standard for C++
- Modern C++ Design by Andrei Alexandrescu
About Me
Провинциал в Москве. 26 лет, женат. В блоге хочется понемногу делиться своими мыслями о программировании на языке C++.
