2014-01-17 2 views
12

При разработке библиотеки C++ я читал, что плохой практикой является включение стандартных библиотечных контейнеров, таких как std::vector, в открытый интерфейс (см., Например, Implications of using std::vector in a dll exported function).Безопасное использование контейнеров в интерфейсе библиотеки C++

Что делать, если я хочу выставить функцию, которая принимает или возвращает список объектов? Я мог бы использовать простой массив, но тогда мне пришлось бы добавить параметр count, что сделает интерфейс более громоздким и менее безопасным. Кроме того, это не помогло бы, если бы я хотел использовать map, например. Я думаю, что библиотеки, такие как Qt, определяют свои собственные контейнеры, которые безопасны для экспорта, но я бы предпочел не добавлять Qt в качестве зависимости, и я не хочу откатывать свои собственные контейнеры.

Какова наилучшая практика борьбы с контейнерами в интерфейсе библиотеки? Может быть, крошечная реализация контейнера (желательно только один или два файла, которые я могу зайти, с разрешительной лицензией), которые я могу использовать как «клей»? Или существует даже способ сделать std::vector и т. Д. Безопасным через границы .DLL/.so и с разными компиляторами?

ответ

3

Вы можете реализовать функцию шаблона. Это имеет два преимущества:

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

Например, это в вашем файле заголовка:

template <typename Iterator> 
void foo(Iterator begin, Iterator end) 
{ 
    for (Iterator it = begin; it != end; ++it) 
    bar(*it); // a function in your library, whose ABI doesn't depend on any container 
} 

Затем пользователи могут вызвать Foo с любым типом контейнера, даже те, которые они изобрели, что вы не знаете.

Один недостаток заключается в том, что вам нужно будет выставить код реализации, по крайней мере, для foo.

Редактировать: вы также сказали, что можете захотеть вернуть контейнер. Рассмотрим варианты, такие как функции обратного вызова, как в золотом старые времена в C:

typedef bool(*Callback)(int value, void* userData); 
void getElements(Callback cb, void* userData) // implementation in .cpp file, not header 
{ 
    for (int value : internalContainer) 
    if (!cb(value, userData)) 
     break; 
} 

Это довольно старая школа «C» способ, но он дает стабильный интерфейс и довольно полезной при практически любой вызывающей (даже фактический код C с незначительными изменениями). Эти две ошибки - это void * userData, чтобы пользователь мог помешать некоторому контексту там (скажем, если они хотят вызвать функцию-член) и тип возврата bool, чтобы позволить обратному сообщению сказать вам остановиться. Вы можете сделать обратный вызов намного более привлекательным с помощью std :: function или что-то еще, но это может привести к поражению некоторых ваших других целей.

+1

Могу ли я предложить «шаблон функции» вместо «шаблон функции»? :) –

4

Фактически это относится не только к контейнерам STL, но применимо практически ко всем типам C++ (в частности, также ко всем другим типам стандартных библиотек).

Поскольку ABI не стандартизирован, вы можете столкнуться со всеми неприятностями. Обычно для каждой поддерживаемой версии компилятора вам необходимо предоставить отдельные двоичные файлы, чтобы они работали. Единственный способ получить действительно портативную DLL - придерживаться простого интерфейса C. Обычно это приводит к чему-то вроде COM, так как вы должны убедиться, что все распределения и соответствующие деаллокации происходят в одном модуле и что никакие детали фактического макета объекта не отображаются пользователю.

+0

Возможно, не все * стандартные типы библиотек. Уверен, вы могли бы уйти с std :: pair, array, bitset и кортежем на связке инструментальных цепей. Но это, безусловно, не гарантировано - я просто думаю, что части stdlib могут быть не токсичными. –

+0

@JohnZwinck: Я бы ** не ** сделал ставку на 'tuple'; libstdC++ и libC++ версии отличаются, если я правильно помню. –

+0

@ComicSansMS: Да, я просто подумал о COM и подумал, как они решили проблему там ... которая ведет вниз по кроличьей дыре от VARIANT и SAFEARRAY.Чем больше я узнаю о COM, тем больше я понимаю, что это так сложно по какой-то причине. – jdm

3

TL; DR Нет проблем, если вы распространяете исходный код или скомпилированные двоичные файлы для различных поддерживаемых наборов (реализация ABI + Standard Library).

В целом, последний считается громоздким (с причинами), таким образом, ориентиром.

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

Настоящее руководство связано с проблемой совместимости с ABI: ABI представляет собой сложный набор спецификаций, который определяет точный интерфейс скомпилированной библиотеки. Она включает в себя прежде всего:

  • расположение памяти структур
  • имя коверкание функций
  • Вызывающих конвенций функций
  • обработки исключений, типа выполнения информации, ...
  • ...

Для получения дополнительной информации, например, отметьте Itanium ABI. В отличие от C, который имеет очень простой ABI, C++ имеет гораздо более сложную площадь поверхности ... и поэтому для него создано множество различных ABI.

Помимо совместимости с ABI, также существует проблема с реализацией стандартной библиотеки. Большинство компиляторов имеют собственную реализацию Стандартной библиотеки, и эти реализации несовместимы друг с другом (они, например, не представляют std::vector одинаково, хотя все реализуют один и тот же интерфейс и гарантии).

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

Cheers: нет проблем, если вы распространяете исходный код и компилируете клиент.

+0

Можете ли вы прокомментировать эту связанную [Q & A] (https://stackoverflow.com/questions/49059675/transitioning-away-from-stdstring-stdostream-etc-in-a-librarys-public-ap)? – metal

0

Если вы используете C++ 11, вы можете использовать cppcomponents. https://github.com/jbandela/cppcomponents

Это позволит вам использовать между прочим std :: vector как параметр или возвращаемое значение в файлах Dll/.so, созданных с использованием разных компиляторов или стандартных библиотек. Посмотрите на мой ответ на аналогичный вопрос для примера Passing reference to STL vector over dll boundary

Примечание для примера, вам нужно добавить CPPCOMPONENTS_REGISTER(ImplementFiles) после CPPCOMPONENTS_DEFINE_FACTORY() заявления

Смежные вопросы