不同 C++ 文件中的相同类名

2021-12-09 00:00:00 class c++ name-clash

如果两个 C++ 文件对同名类的定义不同,那么在编译和链接时,即使没有警告也会抛出一些东西.例如,

If two C++ files have different definitions of classes with the same name, then when they are compiled and linked, something is thrown out even without a warning. For example,

// a.cc
class Student {
public:
    std::string foo() { return "A"; }
};
void foo_a()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

当使用 g++ 编译和链接在一起时,两者都会输出A"(如果在命令行顺序中 a.cc 在 b.cc 之前).

When compiled and linked together using g++, both will output "A" (if a.cc precedes b.cc in the command line order).

一个类似的主题是这里.我看到命名空间将解决这个问题,但我不知道为什么链接器甚至不发出警告.如果该类的一个定义具有另一个未定义的额外函数,假设 b.cc 更新为:

A similar topic is here. I see namespace will solve this problem but I don't know why the linker doesn't even shoot a warning. And if one definition of the class has extra function that isn't defined in another, say if b.cc is updated as:

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
    std::string bar() { return "K"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << stu.bar() << std::endl;
}

然后 stu.bar() 运行良好.感谢任何能告诉我编译器和链接器在这种情况下如何工作的人.

Then stu.bar() works well. Thanks to anyone who can tell me how the compiler and linker work in such situation.

另外一个问题,如果类是在头文件中定义的,它们是否应该总是用未命名的命名空间包装以避免这种情况?有副作用吗?

As an extra question, if classes are defined in header files, should they always be wrapped with unnamed namespace to avoid such situation? Is there any side effects?

推荐答案

这违反了一个定义规则(C++03, 3.2/5 "One definition rule"),其中说(除其他外):

This is a violation of the one definition rule (C++03, 3.2/5 "One definition rule"), which says (among other things):

一个类类型可以有多个定义(第 9 条),...在一个程序中,只要每个定义出现在不同的翻译单元,并提供定义满足以下条件要求.给定这样一个名为 D 的实体,定义在多个翻译单元,然后

There can be more than one definition of a class type (clause 9), ... 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 named D defined in more than one translation unit, then

  • D 的每个定义都应由相同的记号序列组成;

如果你违反了一个定义规则,行为是未定义的(这意味着可能会发生奇怪的事情).

If you violate the one definition rule, the behavior is undefined (which means that strange things can happen).

链接器看到多个 Student::foo() 的定义――一个在 a 的目标文件中,一个在 b 的目标文件中.然而,它并没有抱怨这一点;它只是选择两者之一(碰巧,它遇到的第一个).这种对重复函数的软"处理显然只适用于内联函数.对于非内联函数,链接器会抱怨多个定义并拒绝生成可执行文件(可能有选项可以放宽此限制).GNU ld 和 MSVC 的链接器都以这种方式运行.

The linker sees multiple definitions of Student::foo() - one in a's object file and one in b's. However it doesn't complain about this; it just selects one of the two (as it happens, the first one it comes across). This 'soft' handling of duplicate functions apparently happens only for inline functions. For non-inline functions, the linker will complain about multiple definitions and will refuse to produce an executable (there may be options that relax this restriction). Both GNU ld and MSVC's linker behave this way.

这种行为是有道理的;内联函数需要在它们使用的每个翻译单元中可用.在一般情况下,它们需要有非内联版本可用(以防调用未内联或函数地址被占用).inline 实际上只是围绕单一定义规则的免费传递 - 但要使其起作用,所有内联定义都需要相同.

The behavior makes some sense; inline functions need to be available in every translation unit they're used in. And in the general case they need to have non-inline versions available (in case the call isn't inlined or if the function's address is taken). inline is really just a free pass around the one-definition rule - but for it to work, all the inline definitions need to be the same.

当我查看目标文件的转储时,我没有看到任何明显的东西向我解释链接器如何知道允许一个函数具有多个定义而其他函数则不允许,但我确定有一些标志或记录就是这样做的.不幸的是,我发现链接器的工作原理和目标文件的详细信息并没有特别详细的记录,因此确切的机制可能对我来说仍然是个谜.

When I look at dumps of the object files, I don't see anything obvious that explains to me how the linker knows that one function is permitted to have multiple definitions and others aren't, but I'm sure there's some flag or record which does just that. Unfortunately, I find that the workings of the linker and object file details aren't particularly well documented, so the precise mechanism will probably remain a mystery to me.

至于你的第二个问题:

作为一个额外的问题,如果在头文件中定义了类,应该它们总是用未命名的命名空间包裹以避免这种情况?有副作用吗?

As an extra question, if classes are defined in header files, should they always be wrapped with unnamed namespace to avoid such situation? Is there any side effects?

您几乎肯定不想这样做,每个类在每个翻译单元中都是一个不同的类型,因此从技术上讲,不能将类的实例从一个翻译单元传递到另一个(通过指针、引用或复制)).此外,您最终会得到任何静态成员的多个实例.那可能不会奏效.

You almost certainly don't want to do this each class would be a distinct type in each translation unit, so technically instances of the class they couldn't be passed from one translation unit to another (by pointer, reference or copying). Also, you'd end up with multiple instances of any static members. That probably wouldn't work well.

将它们放在不同的命名空间中.

Put them in different, named namespaces.

相关文章