Как экспортировать класс, полученный из явно созданного шаблона в 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();
};
Другие вопросы по тегам