11
votes

I'm getting a strange behavior which I don't understand. So I have two different classes with the same name defined in two different cpp files. I understand that this will not cause any error during the compilation of the translation units as they don't know about each other. But shouldn't the linker throw some error when it links these files together?

2
As with a lot of things in C++, if you screw up there isn't necessarily any checking to tell you. You just get undefined behaviour. It's your job to not screw up, you can't always rely on the toolchain to tell you when you screwed up.Jonathan Wakely
Notice that class definitions are generally in header, and then in multiple cpp files via #include.Jarod42

2 Answers

5
votes

You're thinking of the one definition rule. I'm quoting from there (boldface is emphasis of my choosing, not a part of the original document).

Your understanding would be correct--it's illegal to define the same function in multiple compilation units:

One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries). The compiler is not required to diagnose this violation, but the behavior of the program that violates it is undefined.

However, this isn't the case for classes, which can be defined multiple times (up to once in each compilation unit), as long as the definitions are all identical. If they are identical, then you can safely pass instances of that class from one compilation unit to another, since all compilation units have compatible, identical definitions with compatible sizes and memory layouts.

Only one definition of any variable, function, class type, enumeration type, concept (since C++20) or template is allowed in any one translation unit (some of these may have multiple declarations, but only one definition is allowed).

...

There can be more than one definition in a program, as long as each definition appears in a different translation unit, of each of the following: class type, enumeration type, inline function with external linkage inline variable with external linkage (since C++17), class template, non-static function template, static data member of a class template, member function of a class template, partial template specialization, concept, (since C++20) as long as all of the following is true:

  • each definition consists of the same sequence of tokens (typically, appears in the same header file)
  • name lookup from within each definition finds the same entities (after overload-resolution), except that constants with internal or no linkage may refer to different objects as long as they are not ODR-used and have the same values in every definition.
  • overloaded operators, including conversion, allocation, and deallocation functions refer to the same function from each definition (unless referring to one defined within the definition) the language linkage is the same (e.g. the include file isn't inside an extern "C" block)
  • the three rules above apply to every default argument used in each definition
  • if the definition is for a class with an implicitly-declared constructor, every translation unit where it is odr-used must call the same constructor for the base and members
  • if the definition is for a template, then all these requirements apply to both names at the point of definition and dependent names at the point of instantiation

If all these requirements are satisfied, the program behaves as if there is only one definition in the entire program. Otherwise, the behavior is undefined.

The bullet points are a fancy and highly precise way of specifying that the definitions must be the same, in letter and in effective result.

1
votes

The one-definition rule specifically permits this, as long as those definitions are completely, unadulteratedly, identical.

And I do mean absolutely identical. Even if you swap the token struct for the token class, in a case where it would otherwise not matter, your program has undefined behaviour.

And it's for good reason: typically we define classes in headers, and we typically include such headers into multiple translation units; it would be very awkward if this were not allowed.

The same applies to inline function definitions for the same reason.

As for why you don't get an error: well, like I said, undefined behaviour. It would technically be possible for the toolchain to diagnose this, but since multiple class definitions with the same name are a totally commonplace thing to do (per above), it's arguably a waste of time to come up with what would be quite complicated logic for the linker of all things to try to diagnose "accidents". Ultimately, as with many things in this language, it's left up to you to try to get it right.