如果两个C++文件对同名类有不同的定义,那么当它们被编译和链接时,即使没有警告,也会抛出一些东西。例如
// 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 之前(。
类似的话题在这里。我看到命名空间可以解决这个问题,但我不知道为什么链接器甚至不发出警告。如果类的一个定义具有另一个定义未定义的额外函数,则假设 b.cc 更新为:
// 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(( 工作得很好。感谢任何可以告诉我编译器和链接器在这种情况下如何工作的人。
作为一个额外的问题,如果类是在头文件中定义的,是否应该始终使用未命名的命名空间包装它们以避免这种情况?有副作用吗?
违反了一个定义规则(C++03,3.2/5"一个定义规则"(,该规则说(除其他外(:
类类型可以有多个定义(条款 9(,... 在程序中,前提是每个定义出现在不同的 翻译单元,前提是定义满足以下条件 要求。 给定在多个中定义的名为 D 的实体 翻译单元,然后
- D的每个定义应由相同的令牌序列组成;
如果您违反了一个定义规则,则行为是未定义的(这意味着可能会发生奇怪的事情(。
链接器看到Student::foo()
的多个定义 - 一个在 a 的目标文件中,一个在 b 的目标文件中。 但是,它并不抱怨这一点;它只是选择两者之一(碰巧,它遇到的第一个(。这种对重复函数的"软"处理显然只发生在内联函数上。对于非内联函数,链接器将抱怨多个定义,并拒绝生成可执行文件(可能有放宽此限制的选项(。 GNU ld
和 MSVC 的链接器都是这样行为的。
这种行为是有一定道理的;内联函数需要在它们使用的每个翻译单元中可用。在一般情况下,他们需要提供非内联版本(以防调用未内联或函数的地址被占用(。 inline
实际上只是围绕一个定义规则的自由传递 - 但要使其正常工作,所有内联定义都需要相同。
当我查看目标文件的转储时,我没有看到任何明显的内容向我解释链接器如何知道一个函数允许具有多个定义而其他函数不允许,但我确信有一些标志或记录可以做到这一点。不幸的是,我发现链接器和对象文件详细信息的工作原理没有特别好的文档记录,因此确切的机制对我来说可能仍然是个谜。
至于你的第二个问题:
作为一个额外的问题,如果在头文件中定义了类,应该 它们总是用未命名的命名空间包装以避免这种情况? 有副作用吗?
您几乎肯定不想这样做,每个类在每个翻译单元中都是不同的类型,因此从技术上讲,类的实例不能从一个翻译单元传递到另一个翻译单元(通过指针、引用或复制(。 此外,您最终会得到任何静态成员的多个实例。那可能不会很好地工作。
将它们放在不同的命名命名空间中。
您违反了类定义的一个定义规则,并且语言明确禁止这样做。编译器/链接器不需要警告或诊断,在这种情况下,这种情况肯定不能保证按预期工作。
我认为您的"额外"问题是主要问题的线索。
如果我理解您的额外问题,那么我认为您不想将它们包装在命名空间中,因为如果您将同一类 #include 到多个 .cc 文件中,那么您可能只想使用每个方法的一个副本,即使它们是在类中定义的。
这(有点(解释了为什么在主示例中每个函数只有一个版本。 我希望链接器只是假设这两个函数是相同的 - 从同一个 #included 源生成。 如果链接器检测到它们何时不同并发出警告,那就太好了,但我想这很难。
正如Mark B所指出的,实际的答案是"不要去那里"。
除了违反一个定义规则外,你看不到编译器因为名称重整而抱怨C++
编辑:正如康拉德·鲁道夫(Konrad Rudolph(所指出的:在这种情况下,被肢解的名字将是相同的。