开发者

Classes with the same name - is it restricted only within the same translation unit?

开发者 https://www.devze.com 2022-12-30 04:03 出处:网络
Let\'s just I had the following code: foo.h class Foo { // ... }; foo.cpp #include \"foo.h\" // Functions for class Foo defined here...

Let's just I had the following code:

foo.h

class Foo
{
    // ...
};

foo.cpp

#include "foo.h"
// Functions for class Foo defined here...

Let's say that Foo are built into a static library foo.lib.

Now let's say I have the following:

foo2.h

class Foo
{
    // ...
};

foo2.cpp

#include "foo2.h"
// Functions for class Foo defined here...

This is built into a separate static library foo2.lib.

Now, if I re-link foo.lib and foo2.lib into an executable program foo开发者_如何学Python.exe, should it be complaining that class Foo has been defined twice?

In my experiences, neither the compiler or the linker are complaining.

I wouldn't be expecting the compiler to complain, because they have been defined in separate translation units.

But why doesn't the linker complain?

How does the linker differentiate between the 2 versions of the Foo class? Does it work by decorating the symbols?


You can have more than one definition of a class type in multiple translation units subject to some fairly strong restrictions meaning that the definitions must be virtually identical. (3.2 [basic.def.odr])

This also applies to enumeration types, inline functions with external linkage, class template, non-static function template, static data member of a class template, member function of a class template or a template specialization for which some template parameters are not specified.

What this effectively means is that you can follow the common practice of putting a class definition in a header file and using this in multiple translation units so long as there aren't any differences in the include sequence that would cause the token sequence or meaning of any names used in the class definition to differ between the multiple translation units.

What you can't have is more than one definition of a non-inline member function of a class across the whole program.

Violations of either of these rules causes undefined behaviour so no part of the compile sequence is required to generate a diagnostic or cause any particular behaviour so if you have two definitions of a class that are slightly different things my appear to work or may cause strange issues at runtime.

It is very likely that if you have two definitions of a non-inline member function of an identically named class with and identical signature that you will have errors at link time but this isn't a language guarantee.

It's worth noting that if two definitions for the same entities are in separate object files in libraries (either the same or difference libraries) it is likely that your program will not actually include more than one definition for a given entity. The way linkers traditionally work is that the iteratively select object files that help resolve symbols in the program but they leave out object files that don't help resolve symbols. This means that after the first object file with a definition is included there is no need to include the second object file with an alternative definition.


No, neither compiler nor linker required to complain. They might; but it's not required.
See the One Definition Rule.


To cause this to generate a linker error, you'll need to force a symbol with external linkage to occur that's shared between the two versions of foo -- and both those symbols must be used. Since the only symbols from a class that can have external linkage are member functions, you're going to have to have a non-inlined function in each Foo that has the same name.

The practical upshot is: this may work, and it may not, and you're not going to know for any given case until you try (since you can't force the compiler to inline functions). Don't do it.

For example, in the following code you'll get a multiply-defined Foo::Foo at link-time:

foo.h

class Foo
{
public:
    Foo();
    int a;
};

foo2.h

class Foo
{
public:
   Foo();
   int b;
};

foo.cpp

#include "foo.h"
#include <cstdio>

Foo::Foo() : a(22) {}

void do_foo()
{
    Foo foo;
    std::printf("%d\n", foo.a);
}

foo2.cpp

#include "foo2.h"
#include <cstdio>

Foo::Foo() : b(33) {}

void do_foo_2()
{
    Foo foo;
    printf("%d\n", foo.b);
}

main.cpp

extern void do_foo();
extern void do_foo_2();

int main(int argc, char** argv)
{
    do_foo();
    do_foo_2();
}


Part of the issue is that each class is in a static library. Linkers treat static libraries specially and only extract the necessary objects to satisfy missing symbols that have linkage.

Whether you get a linking error or not will depend on what symbols each implementation of foo has and how they are packaged up together. Since you have simple packaging (a single .cpp file for each foo that holds all the methods), it's easy to generate a conflict.

So, let's create a situation where you will get a conflict. You want to define one foo like this:

class foo
{
public:
    foo();
    int a() const;
};

and your second foo like this:

class foo
{
public:
    foo();
    int b() const;
};

Now each of those foo's have one identical symbol with linkage for the default constructor and a second different symbol.

Now in your application, have one function in one file that does:

int lib1()
{
    foo f;
    return f.a();
}

and a second file that does this:

int lib2()
{
    foo f;
    return f.b();
}

Now your application will have three symbols that it needs, foo::foo, foo::a and foo::b. Because of foo::a and foo::b, the linker will try to use both static libraries but will then cause two different copies of foo::foo to get pulled in which will cause the linker to have an error.


You have to include one of those foo headers. The compiler will only know about the included Foo class and perhaps due to name mangling the linker might not complain too.

On the other hand if you include both headers the compiler should complain and the linker might complain anyway.


I believe the compiler should complain before the linker does. Let me explain better.

  • The compiler handles perfectly the compilation of the two separate units, as expected.
  • When the compiler gets a new file referencing both "foo.h" and "foo2.h" (as per Andreas example) there should be an immediate naming conflict.

If the two libraries are referenced separately (no source/header file and dependencies reference both) it might be the case that the program compiles and links correctly due to name mangling, but I am not sure about it.

@Igor (cannot comment... duh!) In the page you ling about the ODR it says the exact contrary of what you stated (see the first of the Examples). You can have multiple entities with the same name and multiple definitions only according to special rules. The number of declarations is also dependent on the kind of entity.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号