-4

I wrote some code trying to understand the relationship between compiling and linking - and also to see where and if function declarations have to be repeated in source files.

The two files I wrote are:

Person.cpp:

#include <string>  
class Person {
  public:
   Person(std::string name) {
   (*this).name = name;
   };
   std::string getName() {
   return name;
   };
   std::string name;
};

and the file

PersonMain.cpp:

#include <iostream>
class Person {
  public:
    Person(std::string);
    std::string getName();
    std::string name;
 };
int main(){
          Person* charlie = new Person("Charlie");
          std::cout << "Hi, my name is " << charlie->getName();
          }

I repeat the Class Person and Class Person function declarations (not the definitions) in the PersonMain.cpp.

I now compile and link the two files using the gcc C++ Compiler:

g++ *.cpp -o runthis.exe

I then get the following error message:

PersonMain.cpp:(.text+0xfe): undefined reference to Person::Person(std::basic_string<char, std::char_traits<char>,std::allocator<char> >)' PersonMain.cpp:(.text+0x13c): undefined reference to Person::getName()' collect2.exe: error: ld returned 1 exit status

It seems the Person Class methods are not found when linking. Why is that? How can I heal this?

ADDENDUM:

I repeated the declaration of Person in PersonMain.cpp explicitly, and didn't but the redeclaration into a header file as would be done normally. So I've done the preprocessor step here already. This faq suggests that:

[The Preprocessor] works on one C++ source file at a time by replacing include directives with the content of the respective files (which is usually just declarations)[...]

and later on:

[The Linker] links all the object files by replacing the references to undefined symbols with the correct addresses. Each of these symbols can be defined in other object files or in libraries.

I add this remark becaue @engf-010 said that a Compilation Unit that has code that isn't actually used in that compilation unit is not compiled, even if it is needed in other compilation units. enf-010 suggests that I put the definition and declaration into the header file, but the faq article says that only the declarations should go there, the definitions can be elsewhere.

yippy_yay
  • 167
  • 1
  • 1
  • 5
  • Please read up about `const` – Ed Heal Aug 30 '17 at 21:12
  • You have two classes named Person. –  Aug 30 '17 at 21:14
  • And you'll get a redefinition error in that case. – LogicStuff Aug 30 '17 at 21:15
  • @LogicStuff But I'm just redeclaring - not redefining- Person in PersonMain. Repeat declarations are okay, I thought. The definition is in Person, that's where the linker should look for it. Normally, all the Re-Declarations would be in a header file, but I put them in explicitly. – yippy_yay Aug 30 '17 at 21:18
  • Okay, I see I'm getting a lot of downvotes - I know I'm doing something wrong, but I still don't understand exactly what. I thought a header file usually contained only declarations - not definitions. The definitions of functions are then contained in .cpp files, that are compiled and can be linked to source files that contain the .h files. If the .h files contained the definitions and the declarations, then the preprocessor would do the linking on the source level. (I'm a beginner, coming from Java). – yippy_yay Aug 30 '17 at 21:30
  • 1
    Erm, please format this mess properly at least. – rfx Aug 30 '17 at 21:42

2 Answers2

2

Usually one splits declarations and definitions into .h and .cpp files respectively.

Your code would look something like this:

person.h - declarations:

#include <string>

class Person {
public:
        Person(std::string n);
        std::string getName() const;

        std::string name;
};

person.cpp - implementation:

#include <string>
#include "person.h"

Person::Person(const char* n) : name(n) { }
std::string Person::getName () const { return name; }

and personmain.cpp - where you use your class Person:

#include <iostream>
#include "person.h"

int main()
{
        Person* charlie = new Person("Charlie");
        std::cout << "Hi, my name is " << charlie->getName ();
}

Like @engf mentioned, essentially, what you got in your code is double definition of Person and no implementation. You can't easily define Person in main file and implement Person::Person() somewhere else (and you should not), in another translation unit (i.e., in person.cpp), without letting the compiler know what you implement. You can't forward-declare Person and use it in personmain.cpp like this:

class Person;
...
Person* charlie = new Person("whatever");

because compiler should know complete type to emit code constructing the Person-object. You can't have two definitions of Person. You can't implement members of Person without definitions, accessible to all users of your code. So, what you left with is the scheme like this above.

To elaborate a bit more on compilation and linking, very roughly, when compiler sees a reference to symbol not defined locally in the same translation unit, it puts appropriate record into object file. When this object file is passed to the linker (possibly, along with a bunch of other object files and libraries), linker should resolve such records to symbols (functions, variables etc.) found somewhere in input object files or libraries. If it succeeds in that, it combines all the referenced code into single image. In your case the linker failed to resolve referenced symbol Person::Person for constructor. Probably this is not bad Q&A to start.

rfx
  • 394
  • 1
  • 6
  • 16
  • I think I might be confused about the use of "Declaration", "Definition" and "Implementation". What you are doing in person.h looks like a Declaration to me, not a definition. And isn't Defintion = Implementation? – yippy_yay Aug 30 '17 at 23:00
  • Sorry about this confusion. You're right. In this context definition = implemenation and declaration = interface. It's my fault (kind of sleepy now :) Going to correct this. – rfx Aug 30 '17 at 23:07
  • I now know what I kept doing wrong: I thought the "class Person{}" stuff in Person.cpp was there to show what namespace it is in. But using that class keyword signals the compiler to make a new class. I thought this wouldn't happen, as no definitions were given here. You pointed me in the right direciton by striking the class Person{} and using Person:: instead. – yippy_yay Aug 30 '17 at 23:22
  • _"class Person{}" stuff in Person.cpp was there to show what namespace it is in_ - no way. You can't tell compiler to use class' namespace like this. You have to use `::` - scope resolution operator here. But anyways, you wouldn't succeed in arranging things this way, because compiler doesn't know what you implement in `person.cpp`. So you're bound to all-in-a-single file or you have to split interface/implementation into `.h` and `.cpp` files. – rfx Aug 30 '17 at 23:30
1

what you've got is a definition in one source file and a declaration in the other source file. When the source file containing the definition is compiled ,the compiler comes to the conclusion that there isn't any 'real' code to generate ,because you didn't use the class. So you end up with an empty object file.

You can define a class in a source file but if you don't use that class in that file it is tossed away as not being used. If you use that class in other source files (through it's declaration) the compiler generates code for member accesses ,but those are not found during linking.

yippy_yay
  • 167
  • 1
  • 1
  • 5
engf-010
  • 3,980
  • 1
  • 14
  • 25
  • That's interesting! Okay, I get that the compiler tosses out code in a compilation unit that is not used in that same compilation unit. How do I tell the compiler that this code will be used in a different compilation unit? (That's what I am trying to do here) – yippy_yay Aug 30 '17 at 21:51
  • @yippy_yay: you can't tell those thing to the compiler. What one normally does is : Put your class definition in a header file and include that header file in the source files that need it. Remember that a class may be defined multiple times (even in the same source) without causing redefinition errors during link. Just as long as those definitions are identical. – engf-010 Aug 30 '17 at 22:12