§ 8. Шаблоны и создание библиотек

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

Схема функционирования библиотек зависит от операционной системы. В Линуксе существует два типа библиотек:

Статические библиотеки представляют собой архив из объектных файлов, созданных компилятором для каждого файла с исходным кодом C++ (файлы с расширением cc, C, cpp, cxx и другие). Динамические библиотеки представляют собой исполняемый файл без функции main и с некоторыми дополнительными полями, таких как версия библиотеки. Расширения файлов являются подсказкой для человека и компилятора, в общем случае можно использовать любые расширения, но тогда о типе файла придется сообщить компилятору посредством отдельного флага.

02Для линковки статической библиотеки достаточно указать полный путь к ней в командной строке линковщика: g++ file1.o file2.o other-files.a. После этого линковщик будет использовать все объектные файлы из архива other-files.a. Для создания самой статической библиотеки из предварительно скомпилированных файлов нужна команда ar: ar -csr other-files.a file3.o file4.o.

03Для линковки динамической библиотеки нужно указать ее имя с помощью флага -l.

g++ file1.o file2.o -lother-files

Тогда линковщик будет искать в системных директориях файлы с названием libother-files.so (файлы с версиями после расширения используются только после установки вашей программы в систему). Если файл найдет, то его содержимое будет использоваться для определения адресов функций, вызываемых по имени в объектных файлах file1.o и file2.o. Для того чтобы добавить директории в список для поиска библиотек, испольуется флаг -L и переменная среды LD_LIBRARY_PATH, которая должна содержать список директорий, разделенных двоеточием. После флагов -l и -L не рекомендуется использовать пробел для совместимости со старыми версиями компиляторов.

В Meson для создания статической или динамической библиотеки достаточно заменить executable на static_library или shared_library соответственно и добавить необязательный параметр version, остальные параметры этих функций такие же, как у executable:

shared_library(
  'other-files',
  sources: ['file3.cc', 'file4.cc'],
  version: meson.project_version()
)

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

Динамические библиотеки, от которых зависит программа, загружаются в память автоматически при запуске этой программы. Загрузка рекурсивна, так что, если библиотека зависит от других библиотек, то они тоже будут загружены автоматически. Загрузить различные версии одних и тех же библиотек одновременно в рамках одной программы невозможно, поскольку это бы привело к конфликту имен. Для обхода этого ограничения некоторые библиотеки добавляют к именам своих символов (функций и глобальных переменных) версию библиотеки. Так делает, например, библиотека libc, которая является оберткой для системных вызовов Линукса. Версионирование символов библиотеки — довольно сложная операция, которой автору не приходилось заниматься, и она здесь не рассматривается.

Задания

  1. Напишите классы для геометрических фигур: окружность, прямоугольник. В каждом классе должны быть поля и методы для доступа к этим полям, которых достаточно для вычисления площади. Классы являются шаблонами с одним параметром — типом числа с плавающей точкой.
    template <class T> class Circle;
    template <class T> class Rectangle;
    
    В заголовочном файле должны быть объявлены только прототипы методов, реализации методов должны находиться в cc-файле. Соберите из этих файлов динамическую библиотеку, в которой должны быть конкретизации шаблонов для чисел с плавающей точкой одинарной и двойной точности (float и double).
  2. Подключите собранную библиотеку к программе и используйте классы для вывода площади произвольных фигур на консоль с одинарной и двойной точностью. Для подключения библиотеки к программе используйте параметр link_with:
    mylib = shared_library(...)
    executable('myapp', link_with: mylib, ...)
    
  3. Напишите функцию my_count, которая вычисляет количество элементов в контейнере, используя итераторы начала и конца (аналог std::distance). Эта функция должна работать с любым типом итераторов, даже если для них не определен оператор вычитания. Для итераторов с произвольным доступом должно использоваться простое вычитание, для остальных итераторов — перебор элементов. Для реализации используйте std::iterator_traits и constexpr if (см. предыдущие слайды).
  4. Напишите тест, которые проверит работу my_count для вектора и списка.