如果两个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的实体,然后
如果你违反了一个定义规则,行为是不确定的(这意味着,奇怪的事情发生)。
链接器发现的多个定义Student::foo()
-一个在的目标文件,一个在B的。 然而,它没有抱怨这一点; 它只是选择两个中的一个(当它发生时,第一一个它遇到)。 重复此功能的“软”处理的唯一显然发生了内联函数。 对于非内联函数,链接器将抱怨多个定义和将拒绝产生一个可执行文件(即放松该限制可能存在选项)。 无论GNU ld
和MSVC的链接行为这种方式。
行为有一定的道理; 内联函数需要在他们所使用的每个翻译单元是可用的。而在一般情况下,他们需要有可用的非嵌入式版本(如果呼叫没有内联,或者函数的地址被占用)。 inline
其实只是围绕着一个定义规则的免费通行证-但它的工作,所有的内联定义需要是相同的。
当我看着目标文件的转储,我没有看到任何明显的向我解释链接器如何知道一个功能允许有多个定义和别人都没有,但我敢肯定有一些标志或记录它做到了这一点。 不幸的是,我发现的连接器和目标文件的细节运作不是特别有据可查的,所以其具体机制可能仍将是一个谜给我。
至于你的第二个问题:
作为一个额外的问题,如果类是在头文件中定义,他们应该始终与具名命名空间包裹,避免这种情况呢? 有没有副作用?
你几乎肯定不希望这样做每一类将在每个翻译单元的独特的类型,他们不能从一个翻译单元传递到另一个类的所以在技术上实例(通过指针,引用或复制)。 此外,你最终与任何静态成员的多个实例。 这可能会无法正常工作。
把它们放到不同,命名的命名空间。
你违反了类的定义和语言明确不允许对这样的一个定义规则。 它不是必需的编译器/连接警告或诊断,并且这样的场景肯定是不能保证预期在这种情况下工作。
但我不知道为什么链接器甚至不开枪警告。
在一个定义规则的违反不需要诊断,因为为了实现诊断,链接器必须证明,这两个定义都没有,实际上就等于。
这是容易的一次的类具有不同的尺寸,不同数量的成员或不同的基类。 但只要所有这些因素都是平等的,你仍然可以有例如不同的成员定义,链接器将不得不使用一些先进的内省对它们进行比较。 即使这总是可能的(我不知道它是,因为对象文件可以使用不同的选项进行编译,从而导致不同的输出),它肯定是非常低效的。
因此,连接器只是被动地接受,你在它扔什么不违反ODR。 不是一个完美的情况,但好。
我觉得你的“额外”的问题是一个线索的主要问题。
如果我理解你的额外的问题,那么我认为你不想将它们包装在一个命名空间,因为如果你#包括同一类分成多个文件的.cc,那么你可能要使用的每个方法的只有一个副本,即使他们被定义在类内部。
这(样的)解释了为什么你只在你的主要例子了解各种函数的一个版本。 我期望接头只是假设两个功能都是相同的 - 由相同的包含(#include)源产生。 这将是很好,如果连接器会检测到时,他们是不同的,并发出警告,但我想这是很难的。
正如马克·B表示,实际的答案是“只要别去那里”。
除了违反了一个定义规则的,你没有看到,因为编译器抱怨名称用C ++压延
编辑:正如鲁道夫康拉德指出:在这种情况下,错位的名称是一样的。