Каков наилучший способ разделить служебные функции в библиотеке, чтобы максимизировать возможность повторного использования? - PullRequest
4 голосов
/ 09 февраля 2010

У меня повторяющаяся проблема со статически связанной библиотекой, которую я написал (или в некоторых случаях код был собран из открытых источников).

Эта библиотека, MFC Toolbox Library по имени, имеет много бесплатных функций, классов и т. Д., Которые поддерживают программирование MFC, API Win32, а также почтенную C-библиотеку и более новый C ++. стандартная библиотека.

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

Например, у меня есть String Utilities.h и String Utilities.cpp , которые предоставляют множество свободных функций, связанных со строками, и даже один или два класса.

И часто я обнаруживаю, что у меня есть пара функций, одна из которых работает без необходимости MFC или его CStrings, а другая функция-брат, которая нуждается в этих вещах. Например:

////////////////////////////////////////////////////////////////////////
// Line Terminator Manipulation
////////////////////////////////////////////////////////////////////////

// AnsiToUnix() Convert Mac or PC style string to Unix style string (i.e. no CR/LF or CR only, but rather LF only)
// NOTE: in-place conversion!
TCHAR * AnsiToUnix(TCHAR * pszAnsi, size_t size);

template <typename T, size_t size>
T * AnsiToUnix(T (&pszBuffer)[size]) { return AnsiToUnix(pszBuffer, size); }

inline TCHAR * AnsiToUnix(Toolbox::AutoCStringBuffer & buffer) { return AnsiToUnix(buffer, buffer.size()); }

// UnixToAnsi() Converts a Unix style string to a PC style string (i.e. CR or LF alone -> CR/LF pair)
CString UnixToAnsi(const TCHAR * source);

Как видите, AnsiToUnix не требует CString. Поскольку Unix использует одиночный возврат каретки в качестве ограничителя строки, а строки ANSI Windows используют CR + LF в качестве ограничителя строки, я гарантирую, что результирующая строка поместится в исходное буферное пространство. Но для обратного преобразования строка почти гарантированно увеличивается, добавляя дополнительный LF для каждого вхождения CR, и, следовательно, желательно использовать CString (или, возможно, std :: string), чтобы обеспечить автоматический рост строка.

Это всего лишь один пример, и сам по себе он не слишком уместен, чтобы рассмотреть возможность преобразования из CString в std :: string, чтобы убрать зависимость от MFC из этой части библиотеки. Тем не менее, есть и другие примеры, когда зависимость гораздо более тонкая, и работа по ее изменению еще труднее. Далее, код хорошо протестирован как есть. Если я пойду и попытаюсь удалить все зависимости MFC, я, скорее всего, внесу в код тонкие ошибки, которые потенциально могут поставить под угрозу наш продукт, и увеличить количество времени, необходимое для выполнения этой, по сути, не совсем необходимой задачи.

Важно отметить, что здесь у нас есть набор функций, очень тесно связанных друг с другом (ANSI-> UNIX, UNIX-> ANSI), но где одна сторона использует MFC, а другая - только использует массивы символов. Итак, если я пытаюсь предоставить заголовок библиотеки, который можно использовать повторно, насколько это возможно, желательно разбить функции, которые все зависят от MFC, на один заголовок и функции, которые не относятся к другому, чтобы было легче распространять указанные файлы в другие проекты, которые не используют MFC (или какую-либо технологию, о которой идет речь: например, было бы желательно иметь все функции, которые не требуют заголовков Win32 - которые просто являются дополнениями к C ++, иметь свой собственный заголовок, и и др.).

Мой вопрос ко всем вам, как вы решаете эти проблемы - Технологическая зависимость и связанные функции находятся в одном месте?

Как вы разбиваете свои библиотеки - делите вещи? Что идет с чем?

Возможно, важно добавить мою мотивацию: я хотел бы иметь возможность публиковать статьи и делиться кодом с другими, но, вообще говоря, они, как правило, используют части библиотеки MFC Toolbox, которые сами используют другие части, создавая глубокая паутина зависимостей, и я не хочу обременять читателя / программиста / потребителя этих статей и проектов кода таким большим багажом!

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


Вот еще один хороший пример:

UINT GetPlatformGDILimit()
{
    return CSystemInfo::IsWin9xCore() ? 0x7FFF : 0x7FFFFFFF;
}

GetPlatformGDILimit () является довольно общей, утилитарной бесплатной функцией. Это действительно не имеет ничего общего с CSystemInfo, кроме как клиент. Так что это не относится к "SystemInfo.h". И это всего лишь одна свободная функция - наверняка никто не попытался бы поместить ее в собственный заголовок? Я поместил его в "Win32Misc.h", в котором есть целый ряд таких вещей - бесплатные функции, которые в основном дополняют Win32 API. Тем не менее, эта, казалось бы, безобидная функция зависит от CSystemInfo, который сам использует CStrings и целый ряд других библиотечных функций, чтобы сделать его способным эффективно выполнять свою работу, или с меньшим количеством строк кода, или более надежно, или со всем вышеперечисленным.

Но если у меня есть демонстрационный проект, который ссылается на одну или две функции в заголовке Win32Misc.h, то я попадаю в границу необходимости извлекать только те отдельные функции, которые нужны проекту (и все, от чего зависят эти функции) и все, от чего зависят эти сущности и т. д.) - или я должен попытаться включить Win32Misc.h и его .cpp - что тянет с собой еще больше нежелательных накладных расходов (только для того, чтобы демо-проект компилировался).

Так какое эмпирическое правило люди используют, чтобы направлять себя относительно того, где провести черту - что с чем? Как сохранить библиотеки C ++ от превращения дерева зависимостей в ад? ;)

Ответы [ 3 ]

1 голос
/ 22 февраля 2010

Лично я бы разбил его на функциональность. Манипуляции со строками в одной библиотеке. Интегральные типы в другом (кроме, возможно, char помещает это в строку lib)

Я бы, конечно, держал вещи, зависящие от платформы, от вещей, не зависящих от платформы. Относящиеся к поставщику вещи от сторонних поставщиков. Это может потребовать двух или даже трех строковых библиотек.

Возможно, вы могли бы использовать парадигму "требует ли она MFC?" все, что требует mfc, должно быть выделено. Затем перейдите к «Требуются ли окна», снова сделайте некоторое разбиение. и так далее ...

Без сомнения, некоторые проекты требуют, чтобы все библиотеки были скомпилированы в VC ++ и выполнялись только в Windows, и это именно так. Другие проекты будут успешно скомпилированы на Linux, используя только подмножество библиотек и скомпилированные с помощью gcc.

DC

0 голосов
/ 19 февраля 2010

В очень расплывчатом ответе KISS - лучшая политика. Однако, кажется, что код зашел слишком далеко и достиг точки невозврата. Это вызывает сожаление, потому что вы хотели бы иметь отдельные библиотеки, которые являются автономными объектами, то есть они не зависят от каких-либо внешних факторов. Вы создаете библиотеку вспомогательных функций MFC и другую библиотеку для других помощников или чего-либо еще. Затем вы решаете, какие вы хотите и когда. Все зависимости находятся внутри каждой библиотеки и являются автономными.

Тогда становится вопросом, какую библиотеку включать или нет.

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

0 голосов
/ 09 февраля 2010

Если вы используете в своих общедоступных интерфейсах только согласованные типы и не используете интерфейсы, отделенные от реализаций, это становится проблемой.

Имейте в виду, что когда вы вводите такую ​​функцию:

std::string ProcessData();

... и поместив исходный код для этого в модуль, отдельный от кода, который будет вызывать его (например, в DLL), вы нарушаете указ об отделении интерфейса от реализации. Это связано с тем, что STL является библиотекой исходного кода, и каждый компилятор, использующий ваши библиотечные функции, может и будет иметь разные реализации и разные двоичные схемы для используемых вами утилит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...