вторник, 15 марта 2011 г.

Я люблю c++

1 void f()
2 {
3 int t;
4 SomeType obj(OtherType(t));
5 obj.method();
6 }

Что такое obj? Казалось бы – переменная типа SomeType. Однако на самом деле – это объявление функции SomeType obj(OtherType t);


Причем на строку 5 MSVC даст вот такую ошибку:


error C2228: left of '.method' must have class/struct/union


Уже не первый раз напарываюсь, но в ступор все равно вгоняет на пару минут…


Чтобы работало, надо заключить OtherType в скобки:


SomeType obj( (OtherType) (t));


Получается так потому, что:


1. Разрешены локальные объявления функций – слабо понятно зачем такое вообще может кому-нить понадобиться.


2. int (t) – это вполне корректное определение переменной t типа int – опять же слабо понятно почему разрешен такой синтаксис.


 

понедельник, 14 марта 2011 г.

shared_ptr vs auto_ptr

Был такой код:

struct IInterf
{
virtual void func() = 0;
};
boost::shared_ptr
<IInterf> PInterf;

PInter createObj();

Через некоторое время стало ясно, что у объекта будет конкретный владелец и я заменил shared_ptr на auto_ptr.


И словил access violation при завершении программы.


Минут через 5 сообразил, что деструктор не виртуальный; shared_ptr динамически создает “удалятель”, зная истинный тип IInterf, а auto_ptr просто зовет delete p.


Когда я писал IInterf, то подумал что он будет всегда обернут в shared_ptr и решил не писать виртуальный деструктор.


Мораль сей басни – не экономьте на спичках :)

пятница, 11 марта 2011 г.

boost::exception

Недавно потребовалось как-то упорядочить исключения в программе. Посмотрел в сторону boost::exception, благо boost  в проекте уже используется.

Библиотечка позволяет решить две проблемы:

  1. Добавлять к исключению любого типа (производного от boost::exception) произвольную информацию, причем не только в месте выброса, но и на более высоких промежуточных уровнях обработки
  2. Клонировать текущее исключение, сохранять его и выкидывать в другом месте, а также “вкладывать” в другое исключение.

Пока остановимся на первом пункте, о втором я расскажу в очередном посте.

Сохранение в исключении произвольной информации

Выглядит примерно так:

1 typedef boost::error_info<struct errinfo_str_, std::string> errinfo_str; // 1
2 typedef boost::error_info<struct errinfo_file_, Handle> errinfo_file;
3
4 ...
5 try
6 {
7 try
8 {
9 throw MyException() << errinfo_file(hfile); // 2
10 }
11 catch(MyException &e)
12 {
13 e << errinfo_str("bla-bla"); // 3
14 throw;
15 }
16 }
17 catch(const boost::esception &e)
18 {
19 std::cout << diagnostic_information(e); // 4
20 if(std::string const * inf=get_error_info<errinfo_str>(e) ) //5
21 std::cout << "Info: " << *inf << "\n";
22 }
23
24

Основная идея – мы можем поэтапно добавлять к исключению информацию. В момент выкидывания исключения часть информации может быть недоступна; она может быть добавлена на верхних уровнях и т.д.


В строке //1 определяется описание пользовательской информации, которую можно сохранить в исключении. Первый аргумент error_info – это уникальный тип, второй – тип данных, который должен храниться в исключении (кстати, вы знали, что можно объявить структуру внутри шаблона? Я – нет).


В строке //2 выкидывается исключение, в котором сохраняется информация о файле.


В строке //3 к перехваченному исключению добавляется новая информация.


В //4 используется функция boost::diagnostic_information, которая формирует строку с описанием исключения и всей присоединенной к нему информации. Строка не особо человеко-читаема и пользователю ее лучше не видеть. Может быть полезно например для сохранения информации в лог.


В //5 происходит проверка на наличии в исключении данных errinfo_str и если они есть, выдается соответствующая строка.


Иерархия исключений


С учетом такой техники классы исключений становятся простыми "маркерами", не содержащими данных или методов. Каждый пользовательский класс исключений наследуется от boost::exception и возможно от std::exception. Чтобы пользовательские исключения могли использовать множественное наследование друг от друга, авторы библиотеки рекомендуют виртуальное наследование.


Удобно также завести свой собственный базовый класс исключений.


Получается примерно вот так:


1 struct MyException : virtual boost::exception, virtual std::exception
2 {
3 const char* what()const throw() { return boost::diagnostic_information_what(*this); }
4 };
5
6 struct MyFileException : virtual MyException {};
7 struct MyNetException : virtual MyException {};
8 struct MyComplexException : virtual MyFileException, virtual MyNetException {};

Использование


В библиотеке определен макрос BOOST_THROW_EXCEPTION, который выкидывает указанное исключение, добавляя к нему имя функции, имя и строку файла. На самом деле он не выкидывает исключение, а вызывает boost::throw_exception. Поведение последней настраивается опциями компиляции, по умолчанию она оборачивает исключение для возможности клонирования.


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


Впечатления


Библиотечка удобна в использовании, относительно невелика и понятно написана.


Однако многие возможности скрыты в недокументированном внутреннем пространстве имен. Например, нельзя перебрать все сохраненные в исключении данные (как это делает diagnostic_information).


Предполагается что остальные части буста используют эту библиотеку для своих исключений, а так как функцию boost::throw_exception можно написать самому, то можно например встроить во все исключения стек-трейс. Однако к сожалению тот же boost::filesystem использует свой собственный макрос, который раскрывается в простой throw. В комментариях написано, что они отказались от boost::exception, так как она дает много лишней информации.

понедельник, 7 марта 2011 г.

Привет

Захотелось попробовать себя на ниве блоггерства.
Я - программист, пишу в основном на С++, немножко на Python и Delphi.


В этом блоге я буду описывать интересные мне проблемы программирования.

Через пару дней, если лень не загрызет, опишу свои недавние разбирательства с boost::exception.