我正在尝试创建一个带有模板化超类的C ++类。我的想法是,我可以从许多具有类似特征的超类中轻松创建许多类似的子类。
我已将有问题的代码提炼如下:
template_test.h
:
template<class BaseClass>
class Templated : public BaseClass
{
public:
Templated(int a);
virtual int Foo();
};
class Base
{
protected:
Base(int a);
public:
virtual int Foo() = 0;
protected:
int b;
};
template_test.cpp
:
#include "template_test.h"
Base::Base(int a)
: b(a+1)
{
}
template<class BaseClass>
Templated<BaseClass>::Templated(int a)
: BaseClass(a)
{
}
template<class BaseClass>
int Templated<BaseClass>::Foo()
{
return this->b;
}
main.cpp
:
#include "template_test.h"
int main()
{
Templated<Base> test(1);
return test.Foo();
}
当我构建代码时,我收到链接器错误,说无法找到符号Templated<Base>::Templated(int)
和Templated<Base>::Foo()
。
Google快速建议将以下内容添加到main.cpp
将解决问题:
template<> Templated<Base>::Templated(int a);
template<> int Templated<Base>::Foo();
但这并不能解决问题。将行添加到main.cpp
也不起作用。 (虽然,有趣的是,将它们添加到两者都会给链接器带来“多重定义的符号”错误,因此它们必须做某事......)
然而,将所有代码放在一个源文件中可以解决问题。虽然这对于上面的点头示例是可以的,但是如果我被迫将所有内容放在一个cpp文件中,那么我正在查看的真实应用程序将变得难以管理。
有谁知道我正在做的事情是否可能? (如何)我可以解决链接器错误?
我认为我可以在class Templated
内联中制作所有方法,这样可行,但这似乎也不理想。
答案 0 :(得分:4)
对于模板化类,必须为使用它的每个翻译单元提供定义。这些定义可以放在单独的文件中,通常使用.inl
或.tcc
扩展名;头文件#include
是底部的文件。因此,即使它在一个单独的文件中,每个翻译单元仍然是#include
d;它不能是独立的。
因此,对于您的示例,将template_test.cpp
重命名为template_test.inl
(或template_test.tcc
或其他),然后#include "template_test.inl"
(或其他)位于{{{ 1}},就在包含守卫的template_test.h
之前。
希望这有帮助!
答案 1 :(得分:2)
问题在于,当编译模板化文件时,编译器不知道生成代码所需的类型,因此它不会。
然后当你链接时,main.cpp说它需要那些函数,但它们从未被编译成目标文件,因此链接器找不到它们。
其他答案显示了以可移植的方式解决此问题的方法,实质上将模板化成员函数的定义放在可以从实例化该类实例的位置看到 - 通过显式实例化或放置在main.cpp中包含#included的文件中的实现。
您可能还想阅读编译器的文档,了解他们建议如何进行设置。我知道IBM XLC编译器有一些不同的设置和选项来设置它们。
答案 2 :(得分:1)
C++ FAQ-lite covers this,以及围绕它的几种方式。
您不必使所有方法都“内联”,但您应该在template_test.h中定义方法体,而不是在template_test.cpp中。
有些编译器可以处理这种分裂,但你必须记住,在一个级别templates are like macros。为了让编译器为您的特定模板生成模板,需要使用模板源。
答案 3 :(得分:-1)
当编译器正在编译main.cpp时,它会看到类定义具有成员函数声明,但没有成员函数定义。它只是假设某个地方必须有“模板化”构造函数和Foo实现的定义,因此它会延迟链接器在链接时找到它。
您的问题的解决方案是将Templated的实现放入template.h。
例如
template<class BaseClass>
class Templated : public BaseClass
{
public:
Templated(int a) : BaseClass(a) {}
virtual int Foo() { return BaseClass::b; }
};
有趣的是,我可以通过将它放在template_test.cpp的末尾来获取代码。
void Nobody_Ever_Calls_This()
{
Templated<Base> dummy(1);
}
现在,编译器可以找到要链接的Templated实例。我不建议将其作为一种技术。其他一些文件可能想要创建
Templated<Widget>
然后你必须向template_test.cpp添加另一个显式实例化。