Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does defining a class across cpp files not cause a linker error?

If I have a file foo.cpp with the following code:

class Foo {
};

class Foo {
};

int main() {
    return 0;
}

Then naturally I get error: redefinition of 'Foo'. However, if I have foo.cpp with

class Foo {
};

int main() {
    return 0;
}

And bar.cpp with

class Foo {
};

Despite class Foo being defined twice across the program, this whole thing compiles fine.

If I had put int something; in both files in global namespace, then I would've gotten a linker error (specifically duplicate symbol), but for class definitions, this never happens.

I know function declarations such as int doIt(); can be duplicated in both cpp files, but a definition, e.g. int doIt() {} cannot be. Now in the first compiler error (with class Foo{}; twice in one cpp file), it said redefinition of foo, so class Foo{}; is a definition. Then why, unlike functions, can it be defined twice in one program?

EDIT: According to this website, named classes have external linkage. So why then is there no clash between class Foo across both cpp files?

EDIT2: According to the website linked above, not only do named classes have external linkage, but so do it's static members. Yet this all compiles fine:

foo.cpp:

class Foo {
public:
    int foo();
    static int x;
};

int Foo::foo() {
    return 5;
}

int main() {
    return 0;
}

bar.cpp:

class Foo {
public:
    int foo(int);
    static bool x;
};

int Foo::foo(int i) {
    return i * 2;
}

Not only has Foo::foo been redefined with a different signature, but Foo::x is of a different type. Both of these should have external linkage yet this code is A-ok.

like image 270
rcplusplus Avatar asked Apr 18 '17 04:04

rcplusplus


Video Answer


2 Answers

Regarding your first question, with identical definitions across multiple TU's, that's explicitly allowed by ODR, because otherwise the language would be useless.

Regarding the second question, with different definitions in different TU's, that is an ODR-violation. However, those are NDR. Your program is still malformed, and it will probably cause weird errors.

Regarding the third question, with static data members, those are declarations, not definitions. They need an unique definition, like:

TheType ClassName::VariableName;

Those are typically placed in the accompanying .cpp file.

There is an exception to that, with const static data members with inline initializers.


ODR = One Definition Rule
TU = Translation Unit
NDR = No Diagnostic Required

A note regarding NDR; some kind of errors are hard for the compiler to detect, and the standard usually do not require that the compiler issue a diagnostic (ie warning or error) in those cases. There are tools, such as CppLint, that can detect many of the errors the compiler can not. When it comes to ODR-violations, those can usually be avoided by only define types in headers.

like image 113
John Smith Avatar answered Oct 01 '22 16:10

John Smith


Because of "One Definition Rule" in C++. You can't redefine class within one translation unit, but class can (and should be) defined in each translation unit which uses it. That's why headers and #include exist in C/C++. You should place class definition in header and include it to each .cpp which uses it. It prevents ODR violation but technically using #include is the same as definition the class in each .cpp file (preprocessor just makes the included file a part of compiled file).

Also pay attention to how definition differs from declaration in C++.

Upd. In your new example with static member variables you have only declarations without definition:

class Foo {
public:
    static int x; // <-- variable declaration
};
int Foo::x; // <-- variable definition

One can duplicate declarations within translation unit but not definitions.

Definition of types (including classes) can be duplicated in different translation units, functions and variables with external linkage - not.

Definition of two types in two translation units with the same name but different structure is ODR violation which linkers usually can't diagnose - your program is incorrect but all "builds just fine".

Translation unit is what the compiler get as an input after preprocessing. Using clang or gcc you can get it like this:

$ clang -E foo.cpp >foo.ii
like image 36
Victor Dyachenko Avatar answered Oct 01 '22 16:10

Victor Dyachenko