In big projects where classes are used in multiple places, we need to define them in those places to use. Here, we can use header files to define the classes and include them in the code files where we want to use them. For example,
main.cpp
#include <iostream>
#include "Point.h"
int main(int argc, char const *argv[])
{
Point{}.print();
return 0;
}Point.h
#ifndef POINT_H
#define POINT_H
class Point
{
private:
int m_x{};
int m_y{};
public:
Point() = default;
Point(int x, int y)
: m_x{x}, m_y{y}
{
}
void print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ")\n";
}
};
#endifNote
Defining class types (or any types) are exempt from ODR.
Classes with many big member functions
If a class has many big member functions, it becomes hard to go through the class. Also, we are mostly interested to know what public interfaces a class provides without understanding how they work. It would be useful to keep the class clean and defining member functions outside of the class. We can keep trivial one liner member functions inside the class as putting them outside will be overdo.
In multiple files scenario where member functions need to be defined outside of the class, we can’t simply put them inside the header file as they still violet ODR. This is because they are not implicitly inline as member functions defined inside classes are implicitly inline. To solve this issue, either we can define them inline or put them in .cpp files.
The issue with putting member functions in the same header file would still lead to some clutter as header file will be big. It should be better to put them inside a separate file altogether.
For example:
Point.cpp
#include <iostream>
#include "Point.h"
void Point::print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ")\n";
}Point.h
#ifndef POINT_H
#define POINT_H
class Point
{
private:
int m_x{};
int m_y{};
public:
Point() = default;
Point(int x, int y)
: m_x{x}, m_y{y}
{
}
void print() const;
};
#endifmain.cpp would be the same as before.
There is one advantage that we got is if there is any change in the implementation of member functions, we only need to change the function in .cpp file and only compile it. It saves us time of compiling the whole program.
We compile the files individually without linking them using the command:
g++-15 -ggdb -std=c++20 -W -c main.cpp # gives object file main.o
g++-15 -ggdb -std=c++20 -W -c Point.cpp # gives object file Point.o
Now, we can link them and create the final executable using command:
g++-15 -ggdb -std=c++20 -W main.o Point.o -o main.out
So, if we change anything in Point.cpp, we just need to recompile it and run the last command of linking and creating executable.