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?
>Solution :
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
Dwith definitions in multiple translation units,
- if
Dis 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.
Solution
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.
Why can’t the linker detect this, but detect duplicate variables?
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.
See Also
- Why does the one definition rule exist in C/C++
- Difference between Undefined Behavior and Ill-formed, no diagnostic message required