【问题标题】:Linker error when using an extern template使用外部模板时出现链接器错误
【发布时间】:2011-12-19 11:46:13
【问题描述】:

我有带有模板的工作代码。与 stl::string 类似,我主要在多个编译单元中使用带有一个参数的模板。为了节省时间,我尝试使用 extern 实例化。但是,如下更改行会产生错误。正确的方法是什么? (P.S. 使用 c++0x 标志在 gcc 上编译)

typedef myTemplate_base<commonType> myTemplate;
extern template class myTemplate_base<commonType>; //using "extern template myTemplate" wont work

我在项目中添加了一个包含以下内容的额外 cpp 文件。

template class myTemplate_base<commonType>;

链接器出现此错误消息(将主文件中第一个对象实例化 (myTemplate someVar;) 的行作为错误源):

未定义的引用'myTemplate_base::~myTemplate_base()'

但是这个类型在类中,定义如下~myTemplate() = default;

编辑:如果您有更好的标题,请发表评论,让合适的人看看这个

Edit2: 有趣的是,添加template class myTemplate_base&lt;commonType&gt; 极大地增加了可执行文件的大小(在 450k 的二进制文件上增加了 100k),即使在主文件中使用了模板(编译 I必须将extern 部分注释掉)。这暗示链接器保留了具有相同实例化的模板的两个实现/我忽略了某些东西。

【问题讨论】:

  • 可能值得说明您正在使用哪个版本的 gcc 以及您正在使用的任何其他编译器标志。编译器标志可能会导致任何代码膨胀。您能否也发布这 2 个文件的精简来源,以供人们自己编写。
  • 因为我现在不在正确的电脑上,所以我可以告诉你,我使用的是与代码块中的 ubuntu 11.10 一起打包的 gcc。一回到另一台电脑,我就会写下实际的版本号和一些示例代码。希望我错过了一些明显的东西。

标签: c++ templates linker c++11


【解决方案1】:

您发现了一个编译器错误。我用g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1 复制了它。解决方法是将析构函数保留为隐式默认值。仅当析构函数被显式标记为默认值 (= default) 时才会出现链接器错误。

无论哪种方式,析构函数都不会使用 extern 模板生成。将模板类标记为 extern,编译器会指出任何需要的符号都是 extern。除了析构函数,它仍然在文件中定义。看起来像添加带有外部模板的= default 会使编译器误以为析构函数将在其他地方定义。

代码膨胀是由 extern 模板引起的。编译器只实例化实际使用的模板类的方法。这通常远小于定义的数字。当您使用 extern template 强制实例化一个类时,编译器会为所有方法发出代码(您刚刚发现的析构函数除外)。


g++ -c -std=c++0x main.cpp
g++ -c -std=c++0x extern.cpp
g++ main.o extern.o

header.hpp

#pragma once

#include <iostream>
#include <string>
#include <typeinfo>

template< typename T >
class Foo
{
public:
   Foo( void ) :
      m_name( typeid(T).name() ),
      m_t( T() )
   { }

   // add or remove this to cause the error
   ~Foo( void ) = default;

   void printer( void )
   {
      std::cout << m_name << std::endl;
   }

   T returner( void )
   {
      return m_t;;
   }

private:
   std::string m_name;
   T m_t;
};

extern template class Foo<int>;

extern.cpp

#include "header.hpp"

template class Foo<int>;

main.cpp

#include "header.hpp"

int main()
{
   Foo<int> fi;
   fi.printer();

   Foo<float> ff;
   ff.printer();
}

【讨论】:

  • 非常感谢,我刚回到家正要写下来。我要报告那个错误。顺便说一句,代码膨胀对我来说仍然很好奇。我实际上在示例中使用了我的所有类函数,因此我不希望有额外的代码。除此之外:链接器有没有办法抛出未使用的函数? P.S.:你是 gcc 背后的大师之一吗?
  • 由于某种原因,我无法重现该错误,这意味着我将一个最小的示例放在一起,并且可以正常编译。您可以发布您的示例代码吗?
  • 链接器将丢弃您未使用的函数,除非您告诉它不要这样做。 .sos 保留所有内容,除非它被标记为内部。但是,如果将其链接到二进制文件中,则所有未使用的符号都应该消失。为了捕捉丢失的符号,我必须检查 .o 文件。
  • 非常感谢,如果你关心这里是我的错误报告:gcc.gnu.org/bugzilla/show_bug.cgi?id=51629 在我最后一次回答之后,我想减少我的代码,而不是从头开始,也到达那里
  • 我相信我可能在 gcc 4.7.2 中遇到过同样的错误,但使用的是复制构造函数和复制&移动赋值运算符而不是析构函数。 FWIW 将内联默认定义减少到只是一个声明,并将函数声明为默认值,这对我来说解决了这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-06
  • 2010-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多