I need help translating a concept from Java to C++.
In Java, when you create a class and give it methods (functions), you must define the function in that class so that it can be properly called by any class instance that might want to.
For example, in the class Employee
you'd declare and define the method salaryRaise(int amount)
.
Whenever an Employee
object wants to use it, it calls Employee.salaryRaise(i)
and Java knows exactly where to find it - in the Employee
class.
In C++, functions are declared in .h
files and then defined somewhere else.
How does the compiler know where to find this method?
That is really the job of the linker, not the compiler. The compiler will call the function referencing a symbol that is yet undefined. The linker will then get the different translation units and resolve the symbols by name (that is, the mangled names that encodes additional information as the namespaces, types of the arguments...)
You declare the function in the header (.h) file. That header file then gets #include
ed in any other files where you need to use the function. This satisfies the compiler, because it knows the signature of the function and how to call it.
The function is then defined in the source (.cpp) file. The source file includes it's own header file, so that when you compile it, you end up with an object file containing the complete compiled code for that function.
The linker then links any parts of your code where you've called the function (i.e. the files where you included the header), with the actual code for that function.
EDIT with example:
Foo.h:
#ifndef FOO_H
#define FOO_H
class Foo
{
public:
int fooFunction(double a);
};
#endif
Foo.cpp:
#include "Foo.h"
int Foo::fooFunction(double a)
{
// Function definition
return 1;
}
Compiling Foo.cpp
generates Foo.obj
which contains the complete definition for fooFunction
. So far so good.
Bar.h:
#ifndef BAR_H
#define BAR_H
#include "Foo.h"
class Bar
{
public:
void barFunction();
};
#endif
Bar.cpp:
#include "Bar.h"
void Bar::barFunction()
{
Foo foo;
int returnValue = foo.fooFunction(2.0);
}
Bar.cpp
includes Bar.h
which in turn includes Foo.h
. When Bar.cpp
gets preprocessed therefore, the declarations for Foo::fooFunction
are inserted at the top of the file. So, when the statement int returnValue = foo.fooFunction(2.0);
is compiled, the compiler knows how to emit the machine instructions to call fooFunction
because it knows the type of the return value (int
) and it knows the types of the parameters (a double
, and an implicit this
pointer for the foo
object). Because no function definition was provided, the function will not be inlined (inlining means the entire code for the function is copied into the point at which it is called). Instead, a pointer to the memory address of the function is used to call it. Because a pointer is being used, the compiler doesn't care about the definition - all it needs to know is "I need to call a function X at memory location Y with parameters A and B, and I need to have an int
sized memory section ready to store the return value, and I'll assume that the code at that address knows how to perform the function". However, the compiler has no way to know what the address of that function will be in advance (because the function definition lives in a separate .cpp
file and will be part of a separate compilation job AKA translation unit).
That's where the linker comes in. Once all the translation units have been compiled (which could be in any arbitrary order), the linker goes back to the compiled code for Bar.cpp
and links the two together by filling in the address for the now compiled definition of fooFunction
at the point at which it is called in Bar.cpp
, thus making the compiled code fully executable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With