Adam Easterling Adam Easterling - 2 months ago 10
C++ Question

When a method is defined inside class scope, project won't compile unless method is invoked somewhere else in the original cpp file

Here's the body of main.cpp

#include "Header.h"

int main()
{
auto f = Foo();
f.bar();
return 0;
}


Here's the body of Header.h

class Foo {
public:
void bar();
};


Here's the body of Source.cpp

#include <iostream>

class Foo {
public:
void bar() {
std::cout << "Foo.bar() called\n";
}
};

void t() {
auto f = Foo();
f.bar(); // If this line isn't here, the project won't compile
}


When I comment out
f.bar();
in Source.cpp, I receive the following error upon compilation. It's telling me that
f.bar()
in
main()
is unresolved:


Error LNK2019 unresolved external symbol "public: void __thiscall
Foo::bar(void)" (?bar@Foo@@QAEXXZ) referenced in function _main ...


I understand that it's common to define methods outside of class scope, and indeed the following version of Source.cpp compiles successfully--

#include <iostream>
#include "Header.h"

void Foo::bar() {
std::cout << "Foo.bar() called\n";
}


Nonetheless, I don't understand what's wrong with the original version. It feels like something mysterious and magical is going on that I don't fully understand.

Answer

Your original program violates the One Definition Rule (ODR), defined in C++14, 3.2. The relevant paragraph is number 6:

There can be more than one definition of a class type [...] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity name D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and
  • [...]

You have two translation units: main.cpp with all its header files (including Header.h), and Source.cpp with all its header files (which do not include Header.h). Both translation units contain a definition of the class ::Foo: main.cpp contains the one from Header.h, and Source.cpp has its own.

However, the two do not consist of the same sequence of tokens. The one in Source.cpp contains an inline function definition.

The reason for having one definition of a class in a header and only getting it by including this header is two-fold:

  • You save the effort of duplicating it, and more importantly, finding every duplicate if you have to change it.
  • Barring preprocessor shenanigans, with there being only one textual form of the definition, you cannot run afoul of the ODR.

Note that ODR violations are "no diagnostic required" - in your case you get an error, but as you saw, the error disappears if you call the function in Source.cpp. That doesn't mean your program became correct; it just means that the compiler was no longer capable of discovering the error; nonetheless, very strange behavior could occur. Your program has undefined behavior.