Multiple files error (c++) [Solved]

Started by
8 comments, last by Zahlman 18 years, 8 months ago
Hmmmm, I know this will be a really basic error but I just can't get rid of it. Google didn't help, and no matter how I change the code the error is still there. The errors are:

main.obj : error LNK2019: unresolved external symbol "public: int __thiscall Creature::GetStrength(void)const " (?GetStrength@Creature@@QBEHXZ) referenced in function _main
main.obj : error LNK2019: unresolved external symbol "public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __thiscall Creature::GetName(void)const " (?GetName@Creature@@QBE?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ) referenced in function _main
This is the code (I didn't comment it because it was just supposed to be a little experiment :P). Creature.h

#include <string>
using namespace std;

class Creature
{
private:

	int Strength;
	int Agility;
	int Armor;
	int RedDefence;
	int BlueDefence;
	int GreenDefence;
	string Name;

public:

	Creature(int iStrength, int iAgility, int iArmor, int iRedDefence, int iBlueDefence, int iGreenDefence, string iName);
	~Creature();

    int GetStrength() const;
	int GetAgility() const;
	int GetArmor() const;
	int GetRedDefence() const;
	int GetBlueDefence() const;
	int GetGreenDefence() const; 
	string GetName() const;

	void SetStrength(int iStrength);
	void SetAgility(int iAgility);
	void SetArmor(int iArmor);
	void SetRedDefence(int iRedDefence);
	void SetBlueDefence(int iBlueDefence);
	void SetGreenDefence(int iGreenDefence);
	void SetName(string iName);

};




Creature.cpp

#include "Creature.h"
Creature::Creature(int iStrength, int iAgility, int iArmor, int iRedDefence, int iBlueDefence, int iGreenDefence, std::string iName)
{
	Strength = iStrength;
	Agility = iAgility;
	Armor = iArmor;
	RedDefence = iRedDefence;
	BlueDefence = iBlueDefence;
	GreenDefence = iGreenDefence;
	Name = iName;
}

Creature::~Creature() { }

inline int Creature::GetAgility() const {return Agility;}
inline int Creature::GetArmor() const {return Armor;}
inline int Creature::GetBlueDefence() const {return BlueDefence;}
inline int Creature::GetGreenDefence() const {return GreenDefence;}
inline string Creature::GetName() const {return Name;}
inline int Creature::GetStrength() const {return Strength;}
inline int Creature::GetRedDefence() const {return RedDefence;}

inline void Creature::SetAgility(int iAgility) {Agility = iAgility;}
inline void Creature::SetArmor(int iArmor) {Armor = iArmor;}
inline void Creature::SetBlueDefence(int iBlueDefence) {BlueDefence = iBlueDefence;}
inline void Creature::SetGreenDefence(int iGreenDefence) {GreenDefence = iGreenDefence;}
inline void Creature::SetName(std::string iName) {Name = iName;}
inline void Creature::SetRedDefence(int iRedDefence) {RedDefence = iRedDefence;}
inline void Creature::SetStrength(int iStrength) {Strength = iStrength;}



main.cpp

#include <iostream>
using namespace std;
#include "Creature.h"

int main()
{
	Creature Dave(5,5,5,5,5,5,"Dave");

	cout << Dave.GetName() << "/t/t is:" << endl;
	cout << "/t/t " << Dave.GetStrength() << "/t points strong" << endl;
	cout << endl;

	cin.get();

	return 0;
}



Any help would be appreciated. [Edit: Sorry for the crappy formatting] -Serano
------------
~ Chris (Serano) Watson
If you can dream it, you can do it. - Walt Disney.
Advertisement
In the creature.cpp file, try removing the inline keyword in front of your function definitions.
Thanks SiCrane. The problem now is how would I make them inline? Or should I just not bother?
------------
~ Chris (Serano) Watson
If you can dream it, you can do it. - Walt Disney.
If you want to make them inline you should put the function definitions in the header. Only very smart linkers can inline functions defined in separate translation units; and if your linker is that smart it probably doesn't care if you've declared the function inline or not.
Thanks for the explanation. That was my first time using multiple files so I wasn't too sure about how I would have to change the code.
------------
~ Chris (Serano) Watson
If you can dream it, you can do it. - Walt Disney.
When you mark a function inline, you ask the compiler to replace any call to that function with the contents of the function itself, instead of making that call. In most cases, that also means the function is not generated as a "out-of-line" function at all: there is no GetStrength function in your program.

When calling an inline function, it is necessary for the compiler to have access to the body of the function, so that the replacement can happen. That means you cannot bury a function in a single .cpp file, but really have to make the function code available to any .cpp file that calls that function. The simplest and most obvious way to do so is to put the whole inline function into a header file, which will get included in any translation unit that requires the function (remember that putting something in a header file and then including that header is strictly equivalent to manually writing out all of the header's contents in the file that includes the header - that's how preprocessor #include directives work).

In your case, you declared the functions in your class, telling the compiler that it was not an error to try and call them, but you never did provide the body for them. The compiler therefore assumed, when it processed main.cpp, that the function would be provided by another translation unit that would eventually get linked. However, in Creature.cpp, you did provide those functions as inline functions. Since C++ uses a separate compilation model, when processing Creature.cpp, the compiler is completely unaware of whatever needs main.cpp might have. It notices the functions have been marked inline and yet are never used in Creature.cpp -- it therefore just discards them completely. Once both files are compiled, the linker tries to reconciliate all the function calls, but never finds an actual GetStrength() function, and thus raises a stink.

You have several solutions to this problem. The first is to move the inline functions to the header file that contains the class definition. Any translation unit making use of the class is going to include the header anyway so, that way, they'll get the inline functions too. There are two ways you can do that. You can either simply cut and paste your code back to the header file or, given that member functions which are defined within a class definition are implicitely inline, you could turn the member function declarations into definitions:

class Foo{   int bar;public:   // int GetBar();                <--- That's a declaration   int GetBar() { return bar; } // <--- That's a definition};


The other solution, simpler but probably less satisfying for mere "accessor" functions (which, for such a simple class are in my opinion a terrible idea) is to just drop the inline. That way, actual functions will be generated and thus found by the linker.

edit: Bleh. Too slow... Curse you SiCrane, and your little dog too. [razz]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Thanks for the detailed explanation Fruny. I guess I should probably learn more about compilers/linkers etc... Any recommended reading material?
------------
~ Chris (Serano) Watson
If you can dream it, you can do it. - Walt Disney.
Quote:Original post by Serano
Any recommended reading material?


Short of taking a course on the topic, your compiler documentation is actually probably one of the best place to start. gcc and ld's documentations are also online. They might help you get an idea of what kind of work they actually do. Beyond that, I am not aware of any tutorials explaining how such things work (your Google-Fu is probably as good as mine, you know what kind of things to search for). Then it's a question of experience, and trying to understand and remember why the compiler or linker might flag something as an error.

For example, along with "unresolved external" ('resolving' is the process by which the linker tries to associate a function call in an object file with the actual address of the function. An 'external' is a function (or a variable, for that matter) which has been declared in a translation unit (a .cpp file and all the headers it included), but not defined and thus assumed by the compiler as being provided by another translation unit), another very common error is "multiply defined symbol", where multiple translation units define, well, a symbol (a function, a variable...) and then, comes link time, the linker sees multiple copies and doesn't know which one to pick (it refuses to choose). A common source for that error is to have a non-inline (non-template) function (or a variable definition) in a header file ... which gets included into several .cpp files (thus multiple translation units).

Simple, really, if you keep track of what's going on. [smile]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
You can also read the C++ Standard and see what it says about defining things in multiple translation units and so on. It's fairly dry reading, but it does tell you how things are supposed to fit together. Section 7.1 is most relevant for this case.
Quote:Original post by Fruny
mere "accessor" functions (which, for such a simple class are in my opinion a terrible idea)


Quoted for emphasis. Be aware that the current accessors and mutators do nothing that a public member access wouldn't, except add complication. Any time you find yourself writing that much for something simple - especially when it looks that repetitive - it should be adding a bunch of real value (protection, like sanity checks for values). Here, there is none.

Of course, public data isn't necessarily a good choice here either. Class interface design requires a fair bit of thought, and few people out there will make an effort to teach it, but basically you need to think about what changes will be valid during an objects lifetime, and what *patterns* of access are likely. In particular, most behaviour that uses Creature data should be represented with Creature member functions.

// badvoid levelUpCreature(Creature c) {  // or worse, writing the effective equivalent of this in-line somewhere else  if (coinFlip()) { c.strength++; sanityCheckStrengthOf(c); }  // And similarly for each other stat  // Worse yet, most people who write code like this won't even think to make  // a function for the sanity checks - they might not even think to make a   // function for the levelling-up, and instead copy-paste it a few times.  // One of the reasons OO works as a code organization tool is that it  // highlights more obvious places to put the common, reusable bits. :)}// also bad - in the case that there are no sanity checks at all, worse!int Creature::GetStrength() const {return Strength;}int Creature::SetStrength(int iStrength;) const {  Strength = iStrength;  sanityCheckStrength(); // normally this would be inline, but I just put  // a function stub here because I don't know what checks you're going to want}// and similarly for each statvoid levelUpCreature(Creature c) {  // or worse, writing the effective equivalent of this in-line somewhere else  if (coinFlip()) { c.setStrength(c.getStrength() + 1); }  // where appropriate sanity checks are done in setStrength  // And similarly for each other stat}// goodvoid Creature::levelUp() {  if (coinFlip()) { ++Strength; sanityCheckStrength(); }  // and so on}// Now we have way less code dedicated to moving data around in controlled ways,// because we're not moving the data around - but instead using it in the place// where it *should* be. We're also limiting the interface to what should be// available: a creature's stats only change in certain specific circumstances,// each of which has a logical name (like "levelUp()") and a specific set of// changes that may occur.


Of course, you may correctly point out that this approach will regardless leave duplication (not shown ;) ) in the levelling-up process, since each stat will probably be handled in a similar manner. Good! You're getting the idea now - what we need is to represent the concept of a "Stat" as a class (a very simple one probably), and then we can represent our Creature's data as an array (or some other container if appropriate; but do the simplest thing first) of Stats. Of course, for internal calculations (e.g. when we get to write something like Creature::use(const Item& i) ), we'll need to know which is which, so there will probably be an enumeration in class scope to give names to the array indices.

This topic is closed to new replies.

Advertisement