tags:

views:

1382

answers:

4

I'm getting some really wierd linking errors from a class I wrote. I am completely unable to find anything that will describe what is happening.

Visual Studio (Windows XP)

players.obj : error LNK2019: unresolved external symbol "public: __thiscall TreeNode::TreeNode(void)" (??0?$TreeNode@VPlayer@@@@QAE@XZ) referenced in function "public: __thiscall PlayerList::PlayerList(void)" (??0PlayerList@@QAE@XZ)

Xcode (OSX 10.5)

Undefined symbols: "TreeNode::~TreeNode()", referenced from: PlayerList::~PlayerList()in players.o

Header File: 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;
};

Code from a subclass of TreeNode

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

Are you forgetting to link the object file containing the function bodies for your class functions?

E.g. you'd have something like this in a .cpp:

TreeNode::TreeNode() :
    /* initializers here */
{
    // ...
}

TreeNode::~TreeNode()
{
    // ...
}
strager
+1  A: 

Well you are declaring ~TreeNode(), but are you defining it?

When you declare the destructor you stop the compiler from generating one for you, but you must define it somewhere, even if it is empty.

If your intent was to have an empty destructor you have two options:

-Remove the declaration of ~TreeNode() entirely, and rely on the self generated destructor -Define it as empty. Inling would be very nice here, IE. ~TreeNode() {};

David Reis
+1  A: 

The compiler is complaining about not finding an implementation of the destructor. As pointed before, if you do declare the destructor, the compiler will not automatically generate one for you.

To the suggestion by David Reis of either removing or providing an empty destructor, I would clearly go for the second. If your class is meant to be derived (you have virtual methods) then you should provide a virtual destructor, even if it is empty (same applies to BaseNode).

If you depend on the compiler generated version and user code deletes a derived object through a pointer to a base class whose constructor is not virtual then the derived object's destructor will not be called, possibly leaking resources.

David Rodríguez - dribeas
+3  A: 

When you define your template in a .cpp file, you have to explicitly instantiate it with all the types / template parameters known the template will be used beforehand like this (put it in the .cpp file):

template class TreeNode<Player>;

If you don't know with which template parameters the template will be used, you have to put all the definitions into the header. like

template<typename T>
class TreeNode {
public:
   TreeNode() /* now, also put the code into here: */ { doSomething(); }
};

The reason is that when you are going to use the template from somewhere, the compiler has to be able to generate the code for that specific instantiation of the template. But if you place the code into a .cpp file and compile it, there is no way for the compiler to get its hands on the code to generate the instantiation (except when using the infamous export keyword, which is only supported by very few compilers).

This is also an entry in my C++ Pitfalls answer: http://stackoverflow.com/questions/30373/what-c-pitfalls-should-i-avoid#293047

Johannes Schaub - litb
"there is no way for the compiler to get its hands on the code to generate the instantiation." Unless you use e.g. Comeau C++, where the linker is capable of template instantiation. The technology may reach mainstream compilers, some day, just not today...
Steve Jessop
hehe onebyone :) let's be optimistic all day
Johannes Schaub - litb
btw, the intel compiler also supports this, by the virtue of its fine edison design group frontend.
Johannes Schaub - litb
I'm not saying you were wrong, just that there is a glimmer of hope :-)
Steve Jessop
onebyone, nono i haven't taken it like that. it's all fine. i'm sorry if i sounded harsh in any way :)
Johannes Schaub - litb
I just defined the template in the header file and it compiles now. I hope I can figure out what the heck is going on later.
epochwolf