0
votes

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.

4
in main.cpp Foo::bar() is called but it doesn't have a body. – Hatted Rooster
your source.cpp looks pretty wrong. Thats not how you define class functions there. You may get some random overloading somewhere because you declare the class Foo twice. – Hayt
You shouldn't duplicate the class definition like that. – molbdnilo

4 Answers

3
votes

This is an ODR violation either way, because the class Foo as defined in the header, and the class Foo as defined in the cpp file are not token by token identical.

out of line definition of bar should look like

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

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

Note that ODR violations are 'no diagnostics required'
(this is why one of the examples compiles, the compiler can't always detect ODR violations)

The best way to not accidentally create errors like this is to always include the header-file in the file/files where you implement the methods.

When we are talking about good practices, don't forget the include-guard in the header-file.

If a method is short enough to be inline defined, (style may vary, but for me that means a short oneliner), then that inline definition need to be present in the header-file, so that the compiler sees an identical type every time it encounters it.

2
votes

Take a look at the C++ standard 9.3.2 (n4296):

" ... A member function may be defined (8.4) in its class definition, in which case it is an inline member function (7.1.2), or it may be defined outside of its class definition if it has already been declared but not defined in its class definition. A member function definition that appears outside of the class definition shall appear in a namespace scope enclosing the class definition. Except for member function definitions that appear outside of a class definition, and except for explicit specializations of member functions of class templates and member function templates (14.7) appearing outside of the class definition, a member function shall not be redeclared ..."

& 9.3.3 :

" ...An inline member function (whether static or non-static) may also be defined outside of its class definition provided either its declaration in the class definition or its definition outside of the class definition declares the function as inline.[ Note: Member functions of a class in namespace scope have the linkage of that class. Member functions of a local class (9.8) have no linkage. See 3.5. β€”end note ] ..."

Since bar() is defined inside Foo { }; it is implicitly inline. I believe taking the above clauses into consideration together implies that Foo::bar has local linkage in Source.cpp & is not visible to anyone else. What I find confusing is that calling f.bar() changes the linkage allowing your other example to compile.

2
votes

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.

1
votes
  1. Compiler take the cpp files one by one, there's no way the compiler will remember what else he compiled in another file

  2. main.cpp - include header. Header contains a class declaration. main.obj expects that foo() will be implemented as a separate symbol available at linking time. Done, write the object file and forget

  3. Source.cpp redeclare the class without including the header. As such, the compiler cannot complain of the double declaration. As in the Source.cpp declaration the foo method is implemented inline, the compiler (not knowing anything about what main expects) does not generate a linkage symbol for the method, Done, write the object and forget

  4. linker loads both objects, see that the 'main.obj' needs a linkage symbol for the foo method, searches everywhere and cannot find one - because the Source.obj does not contain one - the compiler was instructed the method will be inline.

Makes sense?