Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why can duplicate definitions of a class in different cpp files be linked with no error?

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

// 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 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

[basic.def.odr]/14

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading