【问题标题】:Why am I getting a Linker error with template function pointer?为什么我收到带有模板函数指针的链接器错误?
【发布时间】:2011-01-21 19:58:35
【问题描述】:

我有一个 EventMgr 类,它有一个模板函数来注册一个监听器。但是,当我注册一个监听器时,链接器给了我一个“error LNK2019: unresolved external symbol”。

开胃菜代码:

class EventMgr {

 template< class T, class EvenT>
 void RegisterListener(T* listener, int EventType, void (T::*MemFunc)(EvenT*) );
}

SoundMgr(它是一个监听器)尝试注册事件:

SoundMgr::SoundMgr(void)
{
  EventManager::GetInstance()->RegisterListener(this, 1, (&SoundMgr::handleBulletFired));
}

我不确定为什么它不会链接。为什么找不到引用类型?

【问题讨论】:

  • 请格式化您的代码。
  • 链接器错误应该提到它无法解析的符号名称。符号的名称是什么?是RegisterListener吗?
  • 1>SoundMgr.obj : 错误 LNK2019: 无法解析的外部符号 "public: void __thiscall EventManager::RegisterListener(class SoundMgr ,int,void (__thiscall SoundMgr: :)(class Event_Bullet_Fired *))" (??$RegisterListener@VSoundMgr@@VEvent_Bullet_Fired@@@EventManager@@QAEXPAVSoundMgr@@HP81@AEXPAVEvent_Bullet_Fired@@@Z@Z) 在函数“public: __thiscallSoundMgr: :SoundMgr(void)" (??0SoundMgr@@QAE@XZ)

标签: c++ templates function-pointers linker-errors


【解决方案1】:

如果您只是在 .h 文件中声明了模板,而实现是在 .cpp 文件中,那么您将遇到此错误,因为 C++ 编译器一次只能运行一个编译单元。当编译器发现您的代码调用了刚刚声明的模板函数时,它将假定具体实例化将由其他编译单元完成(编译器无法知道在哪里可以找到该函数的 .cpp 文件。 .. 编译器一次只能看到一个 .cpp 和所有包含的 .h)。

如果模板参数来自一个众所周知的列表,您可以简单地请求 .cpp 中的程序所需的所有显式实现。

例如,如果你有一个模板函数

template<typename T>
T foo(T x)
{
   ...
}

你知道只需要int foo(int);string foo(string); 那么只使用.h 中的声明就可以了,只要你还在.cpp 中添加两行说:

template<> int foo(int);
template<> string foo(string);

这样做是在告诉编译器要构建什么专业化。如果您以后最终使用其他特化(例如vector&lt;int&gt; foo(vector&lt;int&gt;)),那么您还必须在模板的 .cpp 文件中添加此显式实例化。

但是,在您查看代码的示例中,我猜您事先并不知道将定义哪种事件,因此无法进行这种显式实例化。

另一种解决方案是简单地将整个模板实现放在 .h 文件中,而不是将声明与实现分开。这有时可能不是微不足道的,因为需要您公开更多的实现细节,可能会引入更多的依赖关系。

【讨论】:

  • 我仍然得到同样的错误,即使我给它一个空的身体。
  • 我尝试复制和粘贴您的代码并添加所有缺失的内容,并且编译正常。确定您正在编译的代码不是您发布的代码(它是 EventManagerEventMgr ?),因此很难找到问题所在。
  • 你是对的,它使用的是旧的模块副本。我清理并重建了代码(在确保所有模板函数的标题中都有它们的主体之后),它运行良好。谢谢!所以,作为一项规则,每当我使用模板时,我都需要在声明主体时定义主体?
  • .h 中的所有模板都是一种常见的解决方案,但它的缺点是在模板的客户端中引入了对实现所需的所有知识的依赖(而不仅仅是您需要的知识)用于接口)。拥有一个单独的模板实现可以最大限度地减少依赖关系,但是需要您提前知道您需要哪种专业化,这并不总是可能的。从理论上讲,这种接口/实现分离是exported 模板的工作,但不要去那里......
  • 我可以想象你提到的缺点,因为当我将所有内容都塞进这个头文件(对于 EventManager 模板文件)时,我不得不将我的所有内容从我的 cpp 移到头文件中。我已经对这个想法感到畏缩,但我事先不知道事件的类型,所以我真的别无选择,我猜。非常感谢您的详尽回答,它帮助我理解了为什么以及何时需要模板头文件中的内容。
【解决方案2】:

这可能意味着 RegisterListener 并没有在任何地方实际实现。因为它是一个模板函数,所以你应该在header中实现它。

在标题中尝试以下内容。

template< class T, class EvenT > 
void RegisterListener(T* listener, int EventType, void (T::MemFunc)(EvenT) ) { }

【讨论】:

  • 我在 .cpp 文件中实现如下: template void EventManager::RegisterListener(T* listener, int EventType, void (T::*MemFunc)( EventT*) ) { 查找[EventType] = new FunctionHandler(listener, memFunc);另外,如果我将其注释掉并在标题中创建一个空正文,我仍然会收到相同的错误
  • 如果你在.cpp文件中实现它,它只能在那个cpp文件中使用。在标题中实现。
  • 即使我在标题中给它一个空正文,它仍然给我那个错误
  • 您可以在您的问题中发布您的SoundMgr 标头吗?
  • 在重构所有模板函数以将它们的主体包含在标头中并重新构建代码之后。有效!谢谢詹姆斯
猜你喜欢
  • 1970-01-01
  • 2021-04-14
  • 1970-01-01
  • 1970-01-01
  • 2016-11-04
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多