In src1.cpp, I define a class names "Test" and a function names "f1" that creates a instance of Test.
// src1.cpp
#include <iostream>
class Test {
int a = 1;
public:
Test() {
std::cout << "src1 Test\n";
}
};
void f1() {
Test t;
}
In src1.h, "f1" is exposed.
// src1.h
#pragma once
void f1();
In much the same way, src2.cpp and src2.h is created. A class with the same name and a function that constructs an instance of it.
// src2.cpp
#include <iostream>
class Test {
long a;
public:
Test() {
std::cout << "src2 Test\n";
}
};
void f2() {
Test t;
}
// src2.h
#pragma once
void f2();
Then in the main.cc, I call both f1 and f2.
// main.cpp
#include "src1.h"
#include "src2.h"
int main() {
f1();
f2();
return 0;
}
I compile by the following command with no warning and error.
g++ -Wall -o main main.cpp src1.cpp src2.cpp
And the program output is:
src1 Test src1 Test
It seems compiler allow the different definition of the class Test and both f1 and f2 call the constructor of Test in src1.cpp.
And when I compile with the reverse order like
g++ -Wall -o main main.cpp src1.cpp src2.cpp
And the program output changes to:
src2 Test src2 Test
When I replace the class Test
with a duplicate variable, the compile error occurs.
How does linker deal with the duplicate definitions in that case?
Your code is not allowed, even though you are getting no warning and error.
// a.cpp
class Test {
int a = 1;
// ...
};
// b.cpp
class Test {
long a;
// ...
};
This code violates the one-definition-rule (ODR), because the definitions of Test
must always be the same in different translation units (TUs) (i.e. .cpp
files).
However, odr-violations are a case of ill-formed, no diagnostic required (IFNDR). This means that your code is not valid C++, but the compiler is not required to issue any warning or error. C++ actually has a large amount of these IFNDR situations.
The wording in the C++ standard is this:
For any definable item
D
with definitions in multiple translation units,
- if
D
is a non-inline non-templated function or variable, or- if the definitions in different translation units do not satisfy the following requirements,
the program is ill-formed; [...]
- Each such definition shall consist of the same sequence of tokens
This means that all definitions of T
must be completely identical, essentially copied/pasted versions of each other.
It is very difficult to ensure that you don't violate the ODR manually. This is why you typically put types into headers, so they are guaranteed to be the same in all the cpp files which include them:
// test.hpp
class Test {
int a;
};
// a.cpp
#include "test.hpp"
// b.cpp
#include "test.hpp"
Alternatively, if you want to reuse the Test
name but give it different definitions:
// a.cpp
namespace { // anonymous namespace
class Test {
int a = 1;
// ...
};
}
// b.cpp
namespace {
class Test {
long a;
// ...
};
}
This also solves the problem, because the two Test
classes have internal linkage, meaning that the Test
in a.cpp
and b.cpp
refer to different types. Since they are different types, they can also be defined differently.
The reason is simple: types aren't entities that get emitted like functions and variables, they just exist in the program. They still have to be defined the same everywhere, but the definition of Test
alone produces no assembly at all.
Even if it id, it is difficult to detect an odr-violation, because Test
can be defined in multiple places. It just needs to be defined the same way.
The linker would have to somehow test for equality between two classes, but the C++ standard has very low requirements for linkers.
You only get guaranteed diagnostics when using C++20 modules.