Немного фона
Общие библиотеки в C ++ довольно сложны, потому что стандарт ничего не говорит о них. Это означает, что у каждой платформы есть свой способ их выполнения. Если мы ограничимся Windows и некоторым * nix-вариантом (что-нибудь ELF), различия будут незначительными. Первое отличие - Видимость общего объекта . Настоятельно рекомендуется прочитать эту статью, чтобы получить хороший обзор атрибутов видимости и того, что они для вас делают, что поможет вам избежать ошибок компоновщика.
В любом случае, вы получите что-то похожее (для компиляции со многими системами):
#if defined(_MSC_VER)
# define DLL_EXPORT __declspec(dllexport)
# define DLL_IMPORT __declspec(dllimport)
#elif defined(__GNUC__)
# define DLL_EXPORT __attribute__((visibility("default")))
# define DLL_IMPORT
# if __GNUC__ > 4
# define DLL_LOCAL __attribute__((visibility("hidden")))
# else
# define DLL_LOCAL
# endif
#else
# error("Don't know how to export shared object libraries")
#endif
Затем вам нужно создать общий заголовок (standard.h
?) И добавить в него симпатичную #ifdef
вещь:
#ifdef MY_LIBRARY_COMPILE
# define MY_LIBRARY_PUBLIC DLL_EXPORT
#else
# define MY_LIBRARY_PUBLIC DLL_IMPORT
#endif
Это позволяет помечать классы, функции и все, что угодно, как это:
class MY_LIBRARY_PUBLIC MyClass
{
// ...
}
MY_LIBRARY_PUBLIC int32_t MyFunction();
Это сообщит системе сборки, где искать функции, когда она их вызывает.
Теперь: к фактической точке!
Если вы разделяете константы между библиотеками, вам на самом деле не нужно заботиться о том, дублируются ли они, поскольку ваши константы должны быть небольшими, а дублирование допускает большую оптимизацию (что хорошо). Однако, поскольку вы, похоже, работаете с неконстантами, ситуация немного другая. В C ++ есть миллиард шаблонов для создания кросс-библиотечного синглтона, но мне, естественно, нравится мой путь лучше всего.
В некотором заголовочном файле предположим, что вы хотите поделиться целым числом, поэтому вы должны иметь в myfuncts.h
:
#ifndef MY_FUNCTS_H__
#define MY_FUNCTS_H__
// include the standard header, which has the MY_LIBRARY_PUBLIC definition
#include "standard.h"
// Notice that it is a reference
MY_LIBRARY_PUBLIC int& GetSingleInt();
#endif//MY_FUNCTS_H__
Тогда в файле myfuncts.cpp
вы получите:
#include "myfuncs.h"
int& GetSingleInt()
{
// keep the actual value as static to this function
static int s_value(0);
// but return a reference so that everybody can use it
return s_value;
}
Работа с шаблонами
C ++ имеет супер-мощные шаблоны, и это здорово. Однако распространение шаблонов по библиотекам может быть очень болезненным. Когда компилятор видит шаблон, это сообщение «заполнить все, что вы хотите, чтобы эта работа работала», что прекрасно, если у вас есть только одна конечная цель. Тем не менее, это может стать проблемой, когда вы работаете с несколькими динамическими общими объектами, поскольку теоретически все они могут быть скомпилированы с разными версиями разных компиляторов, каждый из которых считает, что их различные методы заполнения шаблонов правильны (а кто мы такие, чтобы спорить - это не определено в стандарте). Это означает, что шаблоны могут быть огромными болью, но у вас есть некоторые варианты.
Не разрешать разные компиляторы.
Выберите один компилятор (для каждой операционной системы) и придерживайтесь его. Поддерживайте только этот компилятор и требуйте, чтобы все библиотеки были скомпилированы с этим же компилятором. На самом деле это действительно аккуратное решение (оно полностью работает).
Не использовать шаблоны в экспортируемых функциях / классах
Используйте шаблонные функции и классы только при внутренней работе. Это избавляет от многих хлопот, но в целом довольно ограничено. Лично мне нравится использовать шаблоны.
Принудительный экспорт шаблонов и надежда на лучшее
Это работает на удивление хорошо (особенно в сочетании с запретом использования разных компиляторов).
Добавьте это к standard.h
:
#ifdef MY_LIBRARY_COMPILE
#define MY_LIBRARY_EXTERN
#else
#define MY_LIBRARY_EXTERN extern
#endif
И в некотором потребляющем определении класса (перед тем, как объявить сам класс):
// force exporting of templates
MY_LIBRARY_EXTERN template class MY_LIBRARY_PUBLIC std::allocator<int>;
MY_LIBRARY_EXTERN template class MY_LIBRARY_PUBLIC std::vector<int, std::allocator<int> >;
class MY_LIBRARY_PUBLIC MyObject
{
private:
std::vector<int> m_vector;
};
Это почти совершенно идеально ... компилятор не будет кричать на вас, и жизнь будет хорошей, если только ваш компилятор не изменит способ заполнения шаблонов и вы перекомпилируете одну из библиотек, а не другую (и даже тогда, это может все еще работать ... иногда).
Имейте в виду, что если вы используете такие вещи, как частичная специализация шаблонов (или черты типа или какой-либо из более продвинутых элементов метапрограммирования шаблонов), все производители и все их потребители видят одинаковые специализации шаблонов. Например, если у вас есть специализированная реализация vector<T>
для int
с или что-то еще, если производитель видит ее для int
, а потребитель - нет, потребитель с радостью создаст неправильный тип vector<T>
, что приведет к всевозможным ошибкам. Так что будьте очень осторожны.