Класс экспорта MSVC DLL, который наследуется от шаблона, вызывает ошибку LNK2005, уже определенную ошибку
Я отслеживал ошибку в огромном проекте в течение 3 дней и, наконец, получил минимальный воспроизводимый пример. Я хочу поделиться этой проблемой и задать несколько вопросов о странном поведении Visual Studio.
Когда вы экспортируете класс, который наследуется от экземпляра класса шаблона, например,
class __declspec(dllexport) classA : public Template<double>{}
MSVC также будет экспортировать экземпляр класса шаблона Template<double>
в DLL.
Если потребитель включает "template.h"
в своем коде затем создать экземпляр Template<double>
, QD в то же время, ссылку на вышеупомянутую 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 ==========
Если я сделаю ОДИН из следующих изменений, ошибки не будет:
- Перейдите в режим выпуска
- Удалить
AA_API
в(2)
и измените режим связи на STATIC в(1)
- Комментарий
(3)
и раскомментируйте(4)
а также(5)
- Комментарий
(6)
и раскомментируйте(7)
а также(8)
- Компиляция в Linux с помощью gcc
- Изменить параметр проекта в VS: добавить
/FORCE:MULTIPLE
Linker для проекта Main
Вопрос:
Почему изменения 1,2,3,4 и 5 работают? Я думаю, это так странно, что 3 и 4 могут работать.
Как правильно решить эту проблему в Visual Studio?
1 ответ
Я недавно столкнулся с той же проблемой. Мне наконец удалось это исправить, поэтому я поделюсь своими знаниями.
Источник проблемы:
Шаблон создается несколько раз. Это потому, что вы используете неявную реализацию. Один раз, когда вы объявляете класс AA_API classA и один раз в основном, когда вы объявляете шаблон
Это означает, что у вас будет более одного определения для шаблона.
- 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.
Примечание: если класс шаблона находится в другом проекте, вам нужно экспортировать экземпляр.
Я надеюсь, что это решит вашу проблему. Это исправило мой.