Как экспортировать класс, полученный из явно созданного шаблона в Visual Studio?
В моей DLL у меня есть шаблон класса и второй класс, полученный из экземпляра этого шаблона. Оба класса должны быть экспортированы и использованы в других DLL. Компилятор - Visual Studio 2013. Я хочу, чтобы код шаблона создавался ровно в одной единице перевода, поэтому я использую явную реализацию.
Код в DLL1 распространяется следующим образом. Шаблон базового класса:
// In BaseTemplate.h:
#pragma once
template<typename T>
class BaseTemplate
{
public:
T foo(T t);
};
// declare explicit instantiation
extern template class BaseTemplate < int >;
// In BaseTemplate.cpp:
#include "BaseTemplate.h"
// template method definition
template<class T>
T BaseTemplate<T>::foo(T t)
{
return t;
}
// explicit instantiation and export
template class __declspec(dllexport) BaseTemplate < int >;
Производный класс:
// In Derived.h:
#pragma once
#include "BaseTemplate.h"
#ifdef DLL1_EXPORTS // this is predefined in DLL1
#define DLL1_API __declspec(dllexport)
#else
#define DLL1_API __declspec(dllimport)
#endif
class DLL1_API Derived : public BaseTemplate < int >
{
public:
void bar();
};
Теория заключалась в том, что выражение extern предотвращает создание экземпляров во всех единицах перевода, кроме BaseTemplate.cpp, где выполняется явное создание экземпляров. Однако я получаю следующее предупреждение (которое рассматривается как ошибка в моем проекте и, таким образом, нарушает сборку):
1> basetemplate.h(19): warning C4661: 'int BaseTemplate<int>::foo(T)' :
1> no suitable definition provided for explicit template instantiation request
1> with
1> [
1> T=int
1> ]
1> basetemplate.h(15) : see declaration of 'BaseTemplate<int>::foo'
Кажется, что экспорт производного класса запускает инстанцирование, игнорируя оператор extern. Если я удаляю макрос экспорта из класса Derived, DLL1 компилируется без предупреждения (но очевидно, что другие DLL не смогут связываться). Если я собираю член типа BaseTemplate в классе Derived вместо наследования, он также работает (даже с экспортом).
Как можно объединить явное создание экземпляров шаблона и экспортированные производные классы в Visual Studio?
1 ответ
От службы поддержки Microsoft для разработчиков я получил следующий ответ на мой вопрос:
Это текущий дизайн: в этом случае применение __declspec(dllexport) к классу "Производные" означает, что все методы "Производные" и его базовые классы будут экспортированы, и для экспорта метода, который является членом шаблон класса, компилятор должен создать экземпляр шаблона класса и всех его методов. Если определение метода предоставлено в другом месте (как в этом случае), то компилятор не может экспортировать этот метод - отсюда и предупреждение. Примечание: экспорт метода шаблона класса - это больше, чем просто создание его экземпляра - сгенерированный символ имеет другое "связывание" и (немного) другое искаженное имя.
Из этого утверждения и моих собственных экспериментов я пришел к следующему выводу. Всякий раз, когда существует экспортированный производный класс (или шаблон класса), реализация шаблона базового класса должна быть видимой для производного класса. Кроме того, я обнаружил, что extern
одно заявление не заменяет dllimport
, который отсутствовал в примере кода в моем вопросе.
Можно объединить явную реализацию и реализацию только для заголовка. Однако в Visual Studio вы не можете избежать дополнительных реализаций при получении. Вот модель, которую я сейчас использую:
В BaseTemplate.h:
#pragma once
template<typename T>
class BaseTemplate
{
public:
T foo(T t);
};
// Declare explicit instantiation.
#ifdef DLL1_EXPORTS // this is predefined in DLL1
// Avoid duplicate instantation within DLL1, except for inheritance.
extern template class BaseTemplate < int >;
#else
// Provide instantiation for other DLLs.
template class __declspec(dllimport) BaseTemplate < int >;
#endif
// Include implementation in header to enable inheritance
// (and possibly further instantiations in other DLLs).
#include "BaseTemplate_impl.h"
В BaseTemplate_impl.h:
// template method definition
template<class T>
T BaseTemplate<T>::foo(T t)
{
return t;
}
В BaseTemplate.cpp:
#include "BaseTemplate.h"
// explicit instantiation and export
template class __declspec(dllexport) BaseTemplate < int >;
В Derived.h:
#pragma once
#include "BaseTemplate.h"
#ifdef DLL1_EXPORTS // this is predefined in DLL1
#define DLL1_API __declspec(dllexport)
#else
#define DLL1_API __declspec(dllimport)
#endif
class DLL1_API Derived : public BaseTemplate < int >
{
public:
void bar();
};