Класс экспорта MSVC DLL, который наследуется от шаблона, вызывает уже определенную ошибку LNK2005

Я отслеживал ошибку в огромном проекте в течение 3 дней и наконец получил минимально воспроизводимый пример. Я хочу поделиться этой проблемой и задать несколько вопросов о странном поведении Visual Studio.

Когда вы экспортируете класс, который наследуется от созданного экземпляра класса шаблона, например

class __declspec(dllexport) classA : public Template<double>{}

MSVC также экспортирует созданный экземпляр класса шаблона Template<double> в DLL.

Если потребитель включает "template.h" в свой код, а затем создает экземпляр Template<double>, qnd одновременно, ссылку на указанную выше DLL, он получит два определения Template<double>, что вызывает LNK2005 и LNK1169 ошибка. Эта проблема обсуждается в Экспорт Microsoft DLL. и шаблоны C ++.

Вот минимальный воспроизводимый пример этой проблемы (полный код находится здесь):

// ----------- Project AA ------------
// aa/CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(AA)
add_library(AA SHARED classA.cpp)                          //(1)
set_target_properties(AA PROPERTIES COMPILE_FLAGS "-DBUILD_AA")

// aa/config.h
#pragma once

#ifdef _WIN32
#  ifdef BUILD_AA
#    define AA_API __declspec( dllexport )
#  else
#    define AA_API __declspec( dllimport )
#  endif
#else
#   define AA_API
#endif

// aa/template.h
#pragma once
template<class T>
class Template {
public:
    Template() {}
};

//  aa/classA.h
#pragma once
#include "config.h"
#include "template.h"
class AA_API classA : public Template<double> {         //(2)
public:
    int fun();
};

// aa/classA.cpp
#include "classA.h"
int classA::funA(){return 123;}

// ----------- Project Main ----------
//CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(Main)
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
add_subdirectory(aa)
add_executable(Main main.cpp test.cpp)
target_link_libraries(Main PUBLIC AA)

// main.cpp
#include "aa/classA.h"
#include <iostream>
int main(){
    Template<double> a;                                //(3)
    //classA aa;                                       //(4)
    //std::cout << aa.funA() << std::endl;             //(5)
    return 0;
}

// test.cpp
#include "aa/template.h"
class classB {
    Template<double> t;
};
class classC : public Template<double> {};

void fun() {
    Template<double> b;                               //(6)
    //class classB;                                   //(7)
    //class classC;                                   //(8)
}

Я компилирую код в режиме отладки VS2015, он дает многократно определенную ошибку

1>------ Build started: Project: AA, Configuration: Debug x64 ------
1>  classA.cpp
1>  AA.vcxproj -> D:\sandbox\build\aa\Debug\AA.dll
2>------ Build started: Project: Main, Configuration: Debug x64 ------
2>  main.cpp
2>AA.lib(AA.dll) : error LNK2005: "public: __cdecl Template<double>::Template<double>(void)" (??0?$Template@N@@QEAA@XZ) already defined in test.obj
2>D:\sandbox\build\Debug\Main.exe : fatal error LNK1169: one or more multiply defined symbols found
========== Build: 1 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========

Если я сделаю ОДНО ИЗ следующих изменений, ошибки не возникнет:

  1. Перейти в режим выпуска
  2. Удалите AA_API на (2) и измените режим связи на СТАТИЧЕСКИЙ на (1)
  3. Комментируйте (3) и раскомментируйте (4) и (5)
  4. Прокомментируйте (6) и раскомментируйте (7) и (8)
  5. Скомпилировать в Linux с помощью gcc
  6. Измените параметр проекта в VS: добавьте /FORCE:MULTIPLE в Linker для проекта Main

Вопрос:

Почему работают изменения 1,2,3,4 и 5? Я думаю, это так странно, что 3 и 4 могут работать.

Как правильно решить эту проблему в Visual Studio?


person flm8620    schedule 07.07.2017    source источник
comment
Сообщалось ли когда-либо об этой проблеме в Microsoft? Похоже на действительно ужасную проблему без хорошего решения.   -  person Predelnik    schedule 01.04.2020


Ответы (1)


Недавно я столкнулся с той же проблемой. Наконец-то мне удалось это исправить, так что поделюсь своими знаниями.

Источник проблемы:

Шаблон создается несколько раз. Это потому, что вы используете неявное создание экземпляров. Один раз, когда вы объявляете класс AA_API classA, и один раз в основном, когда вы объявляете Template ‹double> a; в основном.

Это означает, что у вас будет несколько определений для шаблона.

  • 1, я не уверен, почему он работает в режиме выпуска (считайте это из-за отсутствия у меня глубоких знаний о шаблонах)
  • 2,3,4, когда вы избавляетесь от любого неявного экземпляра шаблона, вы избавляетесь от нескольких определений
  • 5, может быть, gcc создает экземпляр по-другому, или где-то есть флаг принудительного умножения ... Я не знаю
  • 6. Это не решает вашу проблему, это просто заставляет принимать несколько мгновений.

Решение:

Явное создание экземпляра.

// aa/template.h
#pragma once
template<class T>
class Template {
public:
    Template() {}
};
template class Template<double>;  // Put this line after your template class.

Примечание: если класс шаблона находится в другом проекте, вам необходимо экспортировать экземпляр.

Надеюсь, это решит вашу проблему. Он исправил мою.

person Peter Devenyi    schedule 10.09.2017
comment
Спасибо, что поделились. Но явное создание экземпляра в файле заголовка не решает мою проблему. Если template.h включен в несколько cpp, будет несколько определений. - person flm8620; 17.09.2017
comment
Если я добавлю этот явный экземпляр в файл заголовка, шаг 4 больше не решит мою проблему. - person flm8620; 17.09.2017
comment
Да, похоже, проблема у вас немного другая. К сожалению, я не знаю точного объяснения, но если я заменю конструктор в вашем классе Template конструктором по умолчанию, который он создает. Вам даже не нужно явное создание экземпляра. - person Peter Devenyi; 18.09.2017