模板方法的未定义引用错误

52
这已经让我疯狂了一个半小时。我知道这是一个小问题,但找不到问题出在哪里(当然,下雨的星期五下午并没有帮助)。
我定义了以下类来保存从文件中读取的配置参数,并允许我从程序中访问它们:
class VAConfig {
    friend std::ostream& operator<<( std::ostream& lhs, const VAConfig& rhs);

private:
    VAConfig();
    static std::string      configFilename;
    static VAConfig*        pConfigInstance;
    static TiXmlDocument*   pXmlDoc;
    std::map<std::string, std::string> valueHash;

public:
    static VAConfig* getInstance();
    static void setConfigFileName( std::string& filename ) { configFilename = filename; }
    virtual ~VAConfig();

    void readParameterSet( std::string parameterGroupName );
    template<typename T> T readParameter( const std::string parameterName );
    template<typename T> T convert( const std::string& value );
};

convert() 方法定义在 VAConfig.cpp 中,其作用是

template <typename T>
T VAConfig::convert( const std::string& value )
{
    T t;
    std::istringstream iss( value, std::istringstream::in );
    iss >> t;
    return t;
}

非常简单。但是当我使用我的主程序进行测试时

int y = parameters->convert<int>("5");

我遇到了一个编译错误:undefined reference to 'int VAConfig::convert<int>...'readParameter()也是同样的情况。

我查看了很多模板教程,但是仍然无法解决。有任何想法吗?


4
一个半小时还好,...昨天它折磨我3个小时。 - Keith Pinson
3个回答

92

模板代码实现不应该放在.cpp文件中:编译器必须同时看到调用它们的代码和模板代码的对象代码,除非您使用显式实例化来生成模板的对象代码,但即使如此,.cpp也是错误的文件类型)。

您需要做的是将实现移动到头文件或类似VAConfig.t.hpp的文件中,然后在使用任何模板成员函数时#include "VAConfig.t.hpp"


2
感谢Seth和Dominic,我将实现移动到了头文件中,并且它能正常工作。在我阅读的任何教程中都没有提到这个方面。那么,为什么编译器需要在调用代码同时看到实现?也就是说,泛型函数在这方面有什么独特之处? - recipriversexclusion
2
编译器在实例化模板时需要完整的模板定义 - 以便可以替换模板参数并对其进行评估。 如果您的编译器支持,您可以将模板声明为“extern”,并像使用任何其他成员一样使用它,但需要额外的链接时间工作。GCC支持此扩展。它将成为C++0x标准的一部分。 - greyfade
3
有一些技巧可以避免使用模板,其中之一是前向声明模板特化。-1 表示“绝不应该”。 - rr-
1
你在2009年回答了我的问题,在9年后又帮助了我,非常感谢 :) - IC_
为什么使用显式实例化时,'.cpp'是错误的? - Jimmy T.
我认为我当时(13年前!)的意思是,显式实例化可以补充隐式实例化,因此将定义保留在头文件中,以便稍后在其他类型上进行实例化。当然,如果您100%确定(有时候是这种情况,例如枚举模板),那么只需要.cpp就足够了。 - Seth Johnson

10

如果你将模板方法(convert和readParameter)的实现移动到头文件中,它应该可以工作。

编译器必须在实例化模板函数的地方访问模板函数的实现。


6
一个模板方法只是一个...方法的模板。模板参数在实例化方法时填写。
应该可以构建一个编译器,它满足模板方法的声明,并且有一个“模板编译”步骤来编译所有需要的模板方法实例。
但是,微软的VC并非如此。我听说在Unix上是这种情况。
大多数编译器在请求时实例化模板方法,在源代码中使用它们。为了实例化该方法,编译器必须“看到”模板函数体。这就是为什么通常将函数体放在头文件中或例如.h.cpp文件中的原因,然后将其包含在.h文件的最后一行。

你所描述的是“外部模板”。它已经被GCC作为扩展支持了很长时间,并包含在下一个C++标准版本中。Visual C++应该在下一个版本的当前测试版中支持它。 - greyfade
.h还是.cpp或.hpp? - Naveen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接