вторник, 25 декабря 2012 г.

Удаление указателей из контейнера и их уничтожение

Предположим что ваш последовательный контейнер хранит указатели и вам нужно удалить и уничтожить обьекты удовлетворяющие некоторому критерию. Если указатели не являются умными, то идиому remove + erase использовать не получится поскольку обьекты будут только удалены, но не будут уничтожены. Удаление поштучно является неплохой идеей для std::list, но это будет накладно для std::vector и std::deque, поскольку контейнеру придется многократно перемещать оставшиеся элементы так, чтоб на месте удаленный элементов не оставалось дыр, т.е. в совокупности понадобится O( N2 ) перемещений.
Ниже описано решение проблемы с использованием std::stable_partition в паре с erase. Идея в том, чтобы сначала переместить всех кандидатов на удаление в конец коллекции (оптимизация под вектор), а затем вызвать erase для всех их.
Алгоритмическая сложность: N сравнений + N перестановок в случае, если std::stable_partition сможет использовать внутренний буффер или N log N перестановок в противном случае.
Сначала обьявим функтор для уничтожения элементов в контейнере
template< class TPointer >
struct DeletePointer
{
void operator () ( TPointer item ) { delete item; }
};
Затем переходим к определению самой функции, удаляющей и уничтожающей элементы контейнера
template< class Collection, class Predicate >
void delete_erase_if( Collection& c, Predicate pred ) {
typename Collection::iterator removeBegin = std::stable_partition( c.begin(), c.end(), std::not1 ( pred ) );
std::for_each( removeBegin, c.end(), DeletePointer< typename Collection::value_type > () );
c.erase( removeBegin, c.end() );
}
Поскольку stable_partition переставлят все элементы, удовлетворяющие предекату, в начало, а нам нужно переставлять их в конец, то нам приходится как-то инвертировать предикат. Для этого в теле функции используется std::not1 из модуля <functional>.
Если ваш модуль подключает библиотеку QtCore то вы можете избавиться от функтора DeletePointer заменив вторую строчку в теле delete_erase_if на вызов qDeleteAll.
К минусам приведенной выше реализации относится то, что функция не принимает указатели на функции в качестве предиката. Для того, чтобы обойти данное ограничение, нужно просто обернуть указатель на функцию-предикат в std::ptr_fun.

понедельник, 24 декабря 2012 г.

Указатель на функцию как параметр шаблона

Предположим, что нужно взять указатель на функцию-член класса и параметризовать ею шаблон. Для этого делаем следующее:

Для удобства обьявляем тип указателя на функцию:
typedef QDateTime ( QDateTime::*DateTimeIncrement ) ( int ) const;
Обьявляем шаблон (функцию или класс), принимающий указатель на функцию как параметр
template <DateTimeIncrement Increment>
QDateTime add( QDateTime dateTime, int delta )
{
return ( dateTime.*Increment )( delta );
}
А потом можно заюзать шаблон...
inline QDateTime addDays( QDateTime dateTime, int delta ) { return add< &QDateTime::addDays >( dateTime, delta ); }
...
QDateTime tomorrow = addDays( QDateTime::currentDateTime(), 1 );

воскресенье, 25 ноября 2012 г.

Linux, как указать программе путь к .so библиотеке

Пока я разрабатывал одну библиотеку, мне нужно было как-то тестировать ее без копирования в соответствующие системные каталоги.
Идеальным выглядело решение, когда исполнительный файл с тестами и тестируемая библиотека лежат в одном каталоге, как в Windows. Проблема была в том, что в Linux это не работало - экзешник не мог найти библиотеку.

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

$ LD_LIBRARY_PATH=`pwd` ./executable_name

Если и это не помогает, то попробуйте посмотреть какую именно библиотеку пытается найти ваш экзешник, вызвав команду

$ ldd executable_name

или
$ readelf -d executable_name

Скорее всего просто напросто требуется библиотека с четко прописанной версией в ее названии. Например: libMyLib.so.1 вместо libMyLib.so.1.0.0
Лечится это символическими ссылками:

$ ln -s libMyLib.so.1.0.0 libMyLib.so.1

суббота, 31 марта 2012 г.

Hello RPM

Here I'm giving a brief guidance how to create a simplest RPM file for a QT4 based application. If you want this post to be translated to English just contact me and I will do it upon first request with giving you all possible assistance in urgent case. In return I will expect your help with checking it on grammatical mistakes.

В интернете можно найти много литературы по RPM, о формате .spec файла и о том как пользоваться rpmbuild.
В этом посте я приведу краткое руководство о том как создать простейший RPM пакет не углубляясь в детали. Пост будет особенно интересен тем, кому нужно создать RPM для проекта написанного на Qt.
Итак, у меня есть приложение HelloWorld, написанное на QT 4.8 и доступное для загрузки отсюда. Минимальные требования, для сборки проекта - наличие Qt4.8 и qmake (qt и qt-devel пакеты для Fedora Linux соответственно). 
Комментарии к исходникам:
  1. Когда распакуете архив с исходниками обратите внимание что корневая папка называется "helloworld-1.0". Это первый важный момент. "helloworld" - это название приложения и название пакета, который мы сейчас будем создавать. Нужно чтобы название приложения совпадало с названием пакета. "1.0" - это версия нашего продукта. "-" между названием и версией соответствует соглашению о построении полных названий пакетов. Очень важно чтобы корневая папка именовалась как [название приложения/пакета]-[версия приложения]. Без этого вы получите ошибку на стадии %prep при сборке. От пакетов требуется, чтобы все символы названия были в нижнем регистре поэтому позаботьтесь, чтобы исполнителный файл тоже соответствовал этому требованию.
  2. В файле HelloWorld.pro обратите внимание на строчки:  
    unix {
        target.path = /$(DESTDIR)
        INSTALLS += target
    }
    На основании этой строки qmake сгенерируют инструкцию в Makefile для команды "make install". Назначение макроса DESTDIR я поясню позже. Сейчас же обратите внимание, что он начинается с символа "/". Он необходим, инача qmake допишет в Makefile еще и путь к файлу проекта, что приведет к проблемам на фазе %install.
  3. В корневом каталоге лежит скрипт configure который просто вызывает qmake. Этот скрипт будет автоматически вызываться на стадии %prep и его назначение в том, чтобы подготовить Makefile. Именно поэтому я поместил в него вызов qmake. Проследите, чтобы этот скрипт был помечен как исполняемый файл (chmod +x configure)
Для создания и проверки правильности rpm пакетов вам понадобятся rpmdevtools и rpmlint. Устанавливаем их командой
sudo yum install rpmdevtools rpmlint
Теперь создаем дерево сборки командой
rpmdev-setuptree
В результате у вас появится каталог ~/rpmbuild с дочерними каталогами SPECS, SOURCES, RPMS, SRPMS, BUILD и BUILDROOT. Информацию о назначении каталогов вы найдете на rpm.org.  Полученное дерево каталогов вы можете использовать для построения rpm пакетов для произвольных версий любых программних продуктов.

Скопируйте архив с исходниками в ~/rpmbuild/SOURCES.


Теперь переходим к самому главному. Скачайте spec файл перейдя по следующей ссылке (скачать) и положите его в папку ~/rpmbuild/SPECS.
Перейдите в этот каталог (cd ~/rpmbuild/SPECS) и запустите на исполнение команду
rpmbuild -ba helloworld.spec
В результате в папке RPMS (со смещением на текущую архитектуру) будет лежать ваш RPM с бинарниками, а в папке SRPMS - с исходниками и spec файлом. Если вам нужен только RPM с бинарниками замените -ba  на -bb.

Несколько комментариев к spec файлу:
  1. Поле Name содержит имя rpm файла, который будет сгенерирован. Это имя должно совпадать с названием приложения.
  2. Поле Source0 содержит название нашего архива с исходниками
  3. Обратите внимание на строку 
    make install DESTDIR=$RPM_BUILD_ROOT%{_bindir}
    в разделе %install. Помните, что в HelloWorld.pro мы устанавливали target.path в /$(DESTDIR)? Теперь же мы инициализируем эту переменную путем взятым из контекста сборки и передаем ее команде make install, которая в свою очередь создаст такой каталог (он будет находиться по адресу ~/rpmbuild/BUILDROOT/[полное имя пакета]/usr/bin) и скопирует туда собранный исполняемый файл. 
  4. Строка  %{_bindir}/%{name} в разделе %files инструктирует сборщик, что в результирующий RPM необходимо включить файл с именем helloworld (именно в это значение развернется макрос %name) лежащий со смещением usr/bin (значение макроса %_bindir) относительно $RPM_BUILD_ROOT (т.е. ~/rpmbuild/BUILDROOT/[полное имя пакета]). Без этой строки ваш RPM файл останется пустым.
После запуска команды rpm -i helloworld-1.0-1.fc16.x86_64.rpm программа helloworld будет установлена в папку /usr/bin (значение макроса _bindir)

В данном посте я не затронул тему патчей потому, что я в них сам еще не разобрался. Вернее практически все ясно за исключением того почему нужно класть самые старые исходники в папку с самой последней версией в названии, а потом на все на это накатывать патчи. Как-то немного странно это. Когда все выясню для себя напишу об этом.

Кроме того у описаной процедуры есть недостаток. Если нужно установить несколько файлов из одного пакета в разные места назначения, передача usr/bin через DESTDIR не очень подходит. Возможно лучше было бы захардкодить /usr/bin в  HelloWorld.pro. Другое решение расширить набор входных параметров. В общем тут тоже есь над чем подумать.

Если этот пост был полезен вам, чиркните пару слов. Также буду признателен тем кто будет давать дополнительную полезную информацию на данную тему.