【问题标题】:MSVC DLL exporting class that inherits from template cause LNK2005 already defined error从模板继承的 MSVC DLL 导出类导致 LNK2005 已定义错误
【发布时间】:2017-12-11 03:51:24
【问题描述】:

我在一个大型项目中跟踪了 3 天的错误,终于得到了一个最小的可重现示例。我想分享这个问题,并就 Visual Studio 的奇怪行为提出一些问题。

当您导出从​​实例化模板类继承的类时,例如

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

MSVC 还将在 DLL 中导出实例化的模板类 Template&lt;double&gt;

如果消费者在他的代码中包含"template.h"然后实例化一个Template&lt;double&gt;,同时qnd,链接到上面的DLL,他会得到Template&lt;double&gt;的两个定义,导致LNK2005LNK1169 错误。这个问题在Microsoft DLL export and C++ templates讨论过。

这里是这个问题的最小可重现示例(完整代码是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)
}

我在 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 ==========

如果我做了 ONE OF 以下更改,则不会出现错误:

  1. 更改为发布模式
  2. 删除 AA_API (2) 并将链接模式更改为 STATIC (1)
  3. 评论(3)和取消评论(4)(5)
  4. 评论(6)和取消评论(7)(8)
  5. 通过 gcc 在 Linux 中编译
  6. 在 VS 中更改项目参数:将 /FORCE:MULTIPLE 添加到项目 Main 的链接器

问题:

为什么更改 1、2、3、4 和 5 有效?我觉得 3 和 4 可以工作真是太奇怪了。

如何在 Visual Studio 中正确解决这个问题?

【问题讨论】:

  • 这个问题有没有报告给微软?看起来是一个非常可怕的问题,没有好的解决方案。

标签: c++ visual-studio templates dll lnk2005


【解决方案1】:

我最近遇到了同样的问题。我终于设法解决了它,所以我会分享我的知识。

问题根源:

模板被多次实例化。那是因为您使用隐式实例化。一次是在声明类 AA_API classA 时,一次是在 main 中声明 Template 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.

注意:如果模板类在不同的项目中,需要导出实例化。

我希望这能解决您的问题。它已经修复了我的问题。

【讨论】:

  • 谢谢分享。但是头文件中的显式实例化并不能解决我的问题。如果template.h包含在多个cpp中,就会有多个定义。
  • 如果我在头文件中添加这个显式实例化,第 4 步将不再解决我的问题。
  • 是的,看起来您的问题的性质略有不同。不幸的是,我不知道确切的解释,但是如果我将 Template 类中的构造函数替换为它构建的默认构造函数。您甚至不需要任何显式实例化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-07
  • 2019-12-14
  • 2014-01-22
  • 1970-01-01
  • 1970-01-01
  • 2012-04-25
  • 1970-01-01
相关资源
最近更新 更多