I've been tracking an error in a huge project for 3 days and finally get a minimum reproducable example. I want to share this problem and ask some questions on the weird behavior of Visual Studio.
When you export a class that inherits from a instantiated template class , like
class __declspec(dllexport) classA : public Template<double>{}
MSVC will also export the instantiated template class Template<double>
in DLL.
If the consumer include "template.h"
in his code then instantiate a Template<double>
, qnd at the same time, link to the above DLL, he will get two definition of Template<double>
, which causes LNK2005 and LNK1169 error. This problem is discussed in Microsoft DLL export and C++ templates.
Here is a minimum reproducable example of this problem (Full code is here):
// ----------- 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)
}
I compile the code in VS2015 Debug mode, it gives multiply defined error
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 ==========
If I do ONE OF the following changes, there will be no error:
- Change to Release mode
- Remove
AA_API
at(2)
and change link mode to STATIC at(1)
- Comment
(3)
and uncomment(4)
and(5)
- Comment
(6)
and uncomment(7)
and(8)
- Compile in Linux by gcc
- Change project parameter in VS : add
/FORCE:MULTIPLE
to Linker for project Main
Question:
Why changes 1,2,3,4 and 5 work ? I think it's so weird that 3 and 4 can work.
How to solve this problem properly in Visual Studio ?
I have encountered the same problem recently. I managed to fix it finally so I will share my knowledge.
Source of the problem:
The template is instantiated multiple times. That is because you use implicit instantiation. Once when you declare class AA_API classA and once in the main when you declare Template< double > a; in main.
This means you will have more than one definitions for template.
- 1, I am not sure why it is working in release mode (consider it to the lack of my deep knowledge with templates)
- 2,3,4,when you get rid of any of the implicit template instantiation, you get rid of the multiple definitions
- 5, maybe gcc does the instantiation differently, or there is a force multiply flag somewhere... I don't know
- 6, This does not solve your problem, it just forces to accept the multiple instants.
Solution:
Explicit instantiation.
// aa/template.h
#pragma once
template<class T>
class Template {
public:
Template() {}
};
template class Template<double>; // Put this line after your template class.
Note: if the template class is in a different project, you need to export the instantiation.
I hope this will sort out your problem. It has fixed mine.
来源:https://stackoverflow.com/questions/44960760/msvc-dll-exporting-class-that-inherits-from-template-cause-lnk2005-already-defin