flm8620
flm8620

Reputation: 1501

MSVC DLL exporting class that inherits from template cause LNK2005 already defined error

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:

  1. Change to Release mode
  2. Remove AA_API at (2) and change link mode to STATIC at (1)
  3. Comment (3) and uncomment (4) and (5)
  4. Comment (6) and uncomment (7) and (8)
  5. Compile in Linux by gcc
  6. 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 ?

Upvotes: 6

Views: 1297

Answers (1)

Peter Devenyi
Peter Devenyi

Reputation: 317

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.

Upvotes: 2

Related Questions