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


person Peter H    schedule 04.11.2015    source источник


Ответы (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 >;

В производном.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();
};
person Peter H    schedule 13.11.2015