Общая библиотека: неопределенная ссылка с частичной специализацией шаблона и явным созданием шаблона

Скажем, есть сторонняя библиотека, которая имеет следующее в заголовочном файле:

namespace tpl {
template <class T, class Enable = void>
struct foo {
static void bar(T const&) {
// Default implementation...
};
};
}

В интерфейсе моей собственной библиотеки я должен предоставить частичную специализацию этого foo для моего собственного типа (ов). Итак, допустим, у меня есть:

# include <foo.h>

namespace ml {
struct ML_GLOBAL xxx {
// Whatever...
};
}

namespace tpl {
template <>
struct ML_GLOBAL foo<::ml::xxx> {
static void bar(::ml::xxx const&);
};
}

где ML_GLOBAL это зависит от компилятора атрибут видимости чтобы обеспечить доступность символов для динамического связывания (то есть по умолчанию моя система сборки скрывает все символы в созданной общей библиотеке).

Теперь я не хочу раскрывать свою реализацию barпоэтому я нанимаю явная реализация шаблона:

# include "xxx.h"
namespace tpl {
void foo<::ml::xxx>::bar(::ml::xxx const&) {
// My implementation...
}

extern template struct foo<::ml::xxx>;
}

Когда придет время использовать это tpl::foo<::ml::xxx>::bar функция в некоторых потребительских приложениях (где моя общая библиотека также связана), я получаю неопределенные ссылки ошибка в tpl::foo<::ml::xxx, void>::bar условное обозначение. И действительно, работает nm -CD на созданной общей библиотеке не видно никаких следов tpl::foo<::ml::xxx, void> условное обозначение.

То, что я пробовал до сих пор, были разные комбинации на том, где поставить ML_GLOBAL (например, на явную реализацию самого шаблона, на что GCC явно жалуется в отличие от Clang) и с / без второго аргумента шаблона void,

Вопрос в том, связано ли это с тем фактом, что исходное определение не имеет атрибута видимости (ML_GLOBAL) прикреплен в силу того, что пришел из сторонней библиотеки или я что-то здесь действительно пропустил? Если я ничего не пропустил, то действительно ли я вынужден выставлять свою реализацию в таком сценарии? [… *кашель* больше похоже на недостаток компилятора, если честно *кашель* …]

2

Решение

Это оказалось ложной тревогой. Тем не менее, этот улов занял у меня пару часов, чтобы наконец вспомнить, почему этот символ может быть невидим для потребителей. Это действительно тривиально, но я чувствую, что выкладываю это здесь для будущих посетителей, у которых случается такая же настройка. В основном, если вы используете либо скрипт компоновщика [1] или (чистый) версия скрипта [2] (указывается с --version-script опция компоновщика), то не забудьте установить global видимость для тех tpl::foo* сторонние символы (или что бы то ни было в вашем случае). В моем случае у меня изначально было следующее:

{
global:
extern "C++" {
ml::*;
typeinfo*for?ml::*;
vtable*for?ml::*;
};

local:
extern "C++" {
*;
};
};

что мне явно пришлось изменить

{
global:
extern "C++" {
tpl::foo*;
ml::*;
typeinfo*for?ml::*;
vtable*for?ml::*;
};

local:
extern "C++" {
*;
};
};

чтобы все правильно связать и получить ожидаемый результат.

Надеюсь, что это помогает и с уважением.

БОНУС


Любопытный читатель может спросить: «Какого черта вы комбинируете явные атрибуты видимости и скрипт компоновщика / версии, чтобы контролировать видимость символов, когда уже есть -fvisibility=hidden а также -fvisibility-inlines-hidden варианты, которые должны сделать именно это? «

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

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

В любом случае, оказывается, что, если только те статические библиотеки, которые вы хотите связать с вашей общей библиотекой, сами не были собраны с -fvisibility=hidden а также -fvisibility-inlines-hidden опции (что было бы нелепым ожиданием, поскольку никто не собирается распространять статические библиотеки со скрытыми символами интерфейса по умолчанию, поскольку это противоречит их цели), их символы неизбежно будут все еще видны (например, через nm -CD <shared-library>) независимо от того, что вы сами создаете общую библиотеку с этими параметрами. То есть в этом случае у вас есть два варианта решения:

  1. Вручную перестройте эти статические библиотеки (ваши общие библиотеки) с помощью -fvisibility=hidden а также -fvisibility-inlines-hidden варианты, что явно не всегда возможно / практично, учитывая их потенциальное стороннее происхождение.
  2. Используйте скрипт компоновщика / версии (как это сделано выше) для предоставления во время компоновки, чтобы инструктировать компоновщика принудительно экспортировать / скрывать надлежащие символы из вашей общей библиотеки.
3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]