使用模板实例化[*]时出现以下问题。
文件 foo.h
class Foo
{
public:
template <typename F>
void func(F f)
private:
int member_;
};
文件 foo.cc
template <typename F>
Foo::func(F f)
{
f(member_);
}
文件 caller.cc
Foo::func(boost::bind(&Bar::bar_func, bar_instance, _1));
虽然编译得很好,但链接器会抱怨一个未定义的符号:
void Foo::func<boost::_bi::bind_t...>
如何实例化功能 Foo::func
?由于它需要一个函数作为参数,我有点困惑。我尝试在 foo.cc 中添加实例化函数,因为我习惯使用常规非函数类型:
instantiate()
{
template<> void Foo::func<boost::function<void(int)> >(boost::function<void(int)>);
}
显然,这不起作用。如果有人能指出我正确的方向,我将不胜感激。
谢谢!
[*]是的,我读过parashift FAQ lite。
答案 0 :(得分:5)
答案取决于编译器。某些版本的Sun C ++编译器可以通过构建模板函数实现的缓存来自动处理这些模板,这些实现将在不同的翻译单元之间共享。
如果您使用的是Visual C ++以及任何其他无法执行此操作的编译器,您也可以将函数定义放在标题中。
如果多个.cc文件包含标头,请不要担心重复定义。编译器使用特殊属性标记模板生成的方法,因此链接器知道丢弃重复项而不是抱怨。这就是为什么C ++具有“一个定义规则”的原因之一。
编辑:上述注释适用于一般情况下,您的模板必须能够在给定任何类型参数的情况下进行链接。如果您知道客户端将使用的一组封闭类型,则可以通过在模板的实现文件中使用显式实例化来确保它们可用,这将导致编译器生成要链接的其他文件的定义。但是在一般情况下,您的模板需要使用可能只为客户端所知的类型,那么将模板分成头文件和实现文件几乎没有意义;无论如何,任何客户都需要包括这两个部分。如果要将客户端与复杂的依赖关系隔离,请隐藏非模板化函数后面的依赖关系,然后从模板代码中调用它们。
答案 1 :(得分:3)
将其拆分成文件如您所愿:
不是我推荐这个。只是表明它是可能的。
plop.h
#include <iostream>
class Foo
{
public:
Foo(): member_(15){}
// Note No definition of this in a header file.
// It is defined in plop.cpp and a single instantiation forced
// Without actually using it.
template <typename F>
void func(F f);
private:
int member_;
};
struct Bar
{
void bar_func(int val) { std::cout << val << "\n"; }
};
struct Tar
{
void tar_func(int val) { std::cout << "This should not print because of specialisation of func\n";}
};
Plop.cpp
#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>
template <typename F>
void Foo::func(F f)
{
f(member_);
}
// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Bar, int>, boost::_bi::list2<boost::_bi::value<Bar>, boost::arg<1> (*)()> > myFunc;
// Force the compiler to generate an instantiation of Foo::func()
template void Foo::func<myFunc>(myFunc f);
// Note this is not a specialization as that requires the <> after template.
// See main.cpp for an example of specialization.
的main.cpp
#include "plop.h"
#include <boost/bind.hpp>
#include <iostream>
// Gnarly typedef
typedef boost::_bi::bind_t<void, boost::_mfi::mf1<void, Tar, int>, boost::_bi::list2<boost::_bi::value<Tar>, boost::arg<1> (*)()> > myTar;
// Specialization of Foo::func()
template<> void Foo::func<myTar>(myTar f)
{
std::cout << "Special\n";
}
// Note. This is not instantiated unless it is used.
// But because it is used in main() we get a version.
int main(int argc,char* argv[])
{
Foo f;
Bar b;
Tar t;
f.func(boost::bind(&Bar::bar_func, b, _1)); // Uses instantiation from plop.cpp
f.func(boost::bind(&Tar::tar_func, t, _1)); // Uses local specialization
}
答案 2 :(得分:1)
您是否将foo.cc包含在caller.cc中?实例化是在编译时发生的 - 当编译器在调用者中看到调用时,它会生成模板的实例化版本,但需要提供完整的定义。
答案 3 :(得分:1)
我认为他们所指的是模板函数定义(不仅仅是声明)必须包含在使用它们的文件中。模板函数实际上并不存在,除非/直到它们被使用;如果你把它们放在一个单独的cc文件中,那么编译器不会在其他cc文件中知道它们,除非你明确#include
将cc文件放入头文件或调用它们的文件中,原因是解析器的工作方式。
(这就是为什么模板函数定义通常保存在头文件中,正如Earwicker所描述的那样。)
更清楚吗?
答案 4 :(得分:0)
我相信Earwicker是正确的。在这种情况下显式实例化模板成员函数func的问题是boost :: bind返回的类型是依赖于实现的。它不是不是一个boost :: function。 boost :: function可以包含一个boost:bind,因为它有一个模板赋值运算符,可以推导出右侧的类型(boost :: bind结果)。在caller.cc中func的这种特殊用法中,使用boost的这种特殊实现,boost :: bind的类型实际上是在&lt;之间的链接器错误中提到的类型。和&gt; (即boost::_bi::bind_t...
)。但明确地为该类型实例化func可能会有可移植性问题。