Распространение программного обеспечения и язык С++
Для понимания проблем, связанных с использованием C++ как набора компонентов,
полезно проследить, как распространялись библиотеки C++ в конце 1980-х
годов. Представим себе разработчика библиотек, который создал алгоритм
поиска подстрок за время O(1) (то есть время поиска постоянно,
а не пропорционально длине строки). Это, как известно, нетривиальная задача.
Для того чтобы сделать алгоритм возможно более простым для пользователя,
разработчик должен создать класс строк, основанный на алгоритме, который
будет быстро передавать текстовые строки (fast text strings) в любую программу
клиента. Чтобы сделать это, разработчику необходимо подготовить заголовочный
файл, содержащий определение класса:
// faststring.h
class FastString {
char *m_psz;
public:
FastString(const char *psz);
~FastString(void);
int Length(void) const;
// returns # of characters
// возвращает число символов
int Find(const char *psz) const;
// returns offset
//возвращает смещение
};
После того как класс определен, разработчик должен реализовать его функции-члены
в отдельном файле:
// FastString.cpp
#include "faststring.h"
#include <string.h>
FastString::FastString(const char *psz) : m_psz(new char [strlen(psz) + 1])
{ strcpy(m_psz, psz); }
FastString::~FastString(void)
{ delete[] m_psz; }
int FastString::Length(void) const
{ return strlen(m_psz); }
int FastString::Find(const char *psz) const
{
//O(1) lookup code deleted for> clarity1
// код поиска 0(1) удален для ясности
}
Библиотеки C++ традиционно распространялись в форме исходного кода.
Ожидалось, что пользователи библиотеки будут добавлять реализации исходных
файлов и создаваемую ими систему и перекомпилировать библиотечные исходные
файлы на месте, с использованием своего компилятора C++. Если предположить,
что библиотека написана на наиболее употребительной версии языка C++,
то такой подход был бы вполне работоспособным. Подводным камнем этой схемы
было то, что исполняемый код этой библиотеки должен был включаться во
все клиентские приложения.
Предположим, что для показанного выше класса FastString сгенерированный
машинный код для четырех методов занял 16 Мбайт пространства в результирующем
исполняемом файле. Напомним, что при выполнении O(1)-поиска может потребоваться
много пространства для кода, чтобы обеспечить заданное время исполнения,
- дилемма, которая ограничивает большинство алгоритмов. Как показано на
рис. 1.1, если три приложения используют библиотеку FastString,
то каждая из трех исполняемых программ будет включать в себя по 16 Мбайт
кода. Это означает, что если конечный пользователь инсталлирует все три
клиентских приложения, то реализация FastString займет 48 Мбайт
дискового пространства. Хуже того - если конечный пользователь запустит
все три клиентских приложения одновременно, то код FastString
займет 48 Мбайт виртуальной памяти, так как операционная система не может
обнаружить дублирующий код, имеющийся в каждой исполняемой программе.
Есть еще одна проблема в таком сценарии: когда разработчик библиотеки
находит дефект в классе FastString, нет способа всюду заменить
его реализацию. После того как код FastString скомпонован с клиентским
приложением, невозможно исправить машинный код FastString непосредственно
в компьютере конечного пользователя. Вместо этого разработчик библиотеки
должен известить разработчиков каждого клиентского приложения об изменениях
в исходном коде и надеяться, что они переделают свои приложения, чтобы
получить эффект от этих исправлений. Ясно, что модульность компонента
FastString утрачивается, как только клиент запускает компоновщик
и заново формирует исполняемый файл.
1 В момент написания этого текста автор
не имел работающей версии этого алгоритма, годной для публикации. Детали
такой реализации оставлены как упражнение для читателя.
Динамическая компоновка и С++
C++ и мобильность
Инкапсуляция и С++
Отделение интерфейса от реализации
Абстрактные базы как двоичные интерфейсы
Полиморфизм на этапе выполнения
Расширяемость объекта
Управление ресурсами
Где мы находимся?
|