C++链接错误:使用模板类时出现未定义符号

6
我从自己编写的一个类中得到了一些非常奇怪的链接错误。我完全无法找到任何能够描述正在发生的事情的东西。
Visual Studio(Windows XP) players.obj:error LNK2019:在函数“public: __thiscall PlayerList::PlayerList(void)”(??0PlayerList@@QAE@XZ)中引用了未解析的外部符号“public: __thiscall TreeNode::TreeNode(void)”(??0?$TreeNode@VPlayer@@@@QAE@XZ)。
Xcode(OSX 10.5)
未定义的符号:“TreeNode ::〜TreeNode()”,在 players.o 中引用:PlayerList ::〜PlayerList()。
头文件:generics.h
class TreeNode : public BaseNode{
public:
    const static int MAX_SIZE = -1; //-1 means any size allowed. 
    const static int MIN_SIZE = 0;
    //getters
    int size() const;
    vector<C*> getChildren() const;
    //setters
    void setChildren(vector<C*> &list);
    //Serialization
    virtual void display(ostream &out) const;
    virtual void read(istream &in);
    virtual void write(ostream &out) const;
    //Overrides so SC acts like an array of sorts. 
    virtual C* get(int id) const; 
    virtual int get(C *child) const;
    virtual bool has(C *child) const;
    virtual C* pop(int id);
    virtual void push(C *child);
    virtual TreeNode& operator<< (C *child); //append
    virtual void remove(int id); //Clears memory 
    virtual void remove(C *child); //Clears memory 
    //Initalizers
    TreeNode();
    TreeNode(istream &in);
    TreeNode(long id, istream &in);
    TreeNode(BaseNode* parent, istream &in);
    TreeNode(long id, BaseNode* parent);
    TreeNode(long id, BaseNode* parent, istream &in);
    ~TreeNode();
    string __name__() const{ return "TreeNode"; }
protected:
    void clearChildren();
    void initalizeChildren();
    vector<C*> _children;
};

从TreeNode的子类中编写代码

PlayerList::PlayerList() : TreeNode<Player>(){}
PlayerList::PlayerList(istream &in) : TreeNode<Player>(in){}
PlayerList::~PlayerList(){}

定义TreeNode::~TreeNode()的代码在哪里? - Jim Buck
4个回答

15

当您在.cpp文件中定义模板时,您必须明确地使用所有类型/模板参数进行实例化,以便模板在预先知道将要使用的情况下使用它,如下所示(将其放入.cpp文件中):

template class TreeNode<Player>;

如果您不知道模板将使用哪些模板参数,那么您需要将所有定义放入头文件中。例如:
template<typename T>
class TreeNode {
public:
   TreeNode() /* now, also put the code into here: */ { doSomething(); }
};

原因在于,当您要使用来自某个地方的模板时,编译器必须能够为该特定实例化的模板生成代码。但是,如果将代码放入.cpp文件并进行编译,则编译器无法获取代码以生成实例化(除非使用臭名昭著的“export”关键字,该关键字仅受到极少数编译器的支持)。
这也是我在C++陷阱解答中的一个条目:What C++ pitfalls should I avoid?

除非您使用例如Comeau C++这样的编译器,否则编译器无法获取代码以生成实例。链接器可以进行模板实例化。这项技术可能会在未来某一天被主流编译器采用,但今天还不行... - Steve Jessop
嘿嘿,一个接一个地 :) 让我们整天保持乐观。 - Johannes Schaub - litb
顺便说一下,英特尔编译器也支持这个功能,凭借其精美的 Edison 设计组前端。 - Johannes Schaub - litb
我并不是说你错了,只是有一线希望 :-) - Steve Jessop
一个接一个地,不,我没有那样理解。一切都好。如果我以任何方式听起来很严厉,我很抱歉 :) - Johannes Schaub - litb
我刚刚在头文件中定义了模板,现在已经编译通过了。希望以后能够弄清楚到底发生了什么。 - epochwolf

2

你声明了 ~TreeNode(),但是你是否定义了它?

当你声明析构函数时,你阻止了编译器为你生成一个析构函数,但你必须在某个地方定义它,即使它是空的。

如果你的意图是有一个空的析构函数,你有两个选择:

-完全删除 ~TreeNode() 的声明,并依赖于自动生成的析构函数 -将其定义为空。内联在这里非常好,例如:~TreeNode() {};


构造函数后面缺少 {},哎呀!谢谢你! - Hari Honor

1
编译器抱怨找不到析构函数的实现。如前所述,如果您声明了析构函数,则编译器不会自动生成一个。
对于David Reis建议中删除或提供空析构函数,我会明确选择后者。如果您的类是用于派生的(您有虚拟方法),则应该提供一个虚拟析构函数,即使它是空的(BaseNode也适用)。
如果您依赖于编译器生成的版本,并且用户代码通过指向基类的指针删除派生对象,而该基类的构造函数不是虚拟的,则可能不会调用派生对象的析构函数,从而可能泄漏资源。

0
你是否忘记链接包含类函数体的对象文件?
例如,在 .cpp 文件中,你应该有类似以下的代码:
TreeNode::TreeNode() :
    /* initializers here */
{
    // ...
}

TreeNode::~TreeNode()
{
    // ...
}

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