如果我在A.cpp
和 B.cpp
中有以下代码,则不会生成任何警告或错误,但Initializer::Initializer()
中的B.cpp
会被调用两次虽然A.cpp
中的那个没有被调用。
static int x = 0;
struct Initializer
{
Initializer()
{
x = 10;
}
};
static Initializer init;
由于这违反了一个定义规则并导致未定义的行为,我认为这是完全正常的。但是,当我在一个或两个文件中的类声明之外移动构造函数定义时,如下所示:
static int x = 0;
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
x = 10;
}
static Initializer init;
链接器突然变得聪明到错误并说出one or more multiply defined symbols found
。这里有什么改变,为什么重要?我原本以为链接器总是能够检测到ODR破损 - 什么情况下它不能?
如果我错了,有人会纠正我,但我的理论是,当你有模板化代码(定义总是在标题中)时,你会在许多编译单元中得到重复的定义。它们碰巧都是相同的,所以链接器只选择其中一个而孤立它们并不重要,但是如果有多个定义或模板不起作用则不能错误。
答案 0 :(得分:2)
您的第二个示例明显且易于诊断违反了一个定义规则。它有两个带有外部链接的非内联函数的定义。链接器很容易诊断,因为从链接的目标文件中包含的函数名称中可以看出违规。
您的第一个示例以更微妙的方式打破了一个定义规则。因为类体中定义的函数是隐式声明的inline
,所以必须检查函数体以确定违反了一个定义规则。
仅凭这个原因,我对您的实施未能发现违规行为并不感到惊讶(我没有第一次发现)。显然,在单独查看一个源文件时,编译器无法发现违规,但可能是检测到违规和链接时间的信息实际上并不存在传递给链接器的目标文件。它肯定超出了我期望链接器找到的范围。
答案 1 :(得分:2)
当多个翻译单元对于具有外部链接的项目具有相同的名称签名时,会发生多重定义的符号错误,除非它们是内联函数或方法。
内联函数通常在编译时不受ODR的约束。如果是这样,方法的内联实现将无处不在。但是,在链接时间内追溯应用ODR,因为选择了一个内联函数。因此,具有相同签名的内联函数的行为应该相同。你的内联构造函数违反了这种期望。
相反,如果你在头文件中声明了一个模板,就像这样:
#ifndef I_HH
#define I_HH
tempalte <typename T>
struct Initializer {
Initializer () { x = 10; }
};
#endif
并将其用于A.cpp
和B.cpp
,并在每个中创建了一个静态实例:
static int x;
#include "i.hh"
static Initializer<int> init;
我认为编译器应该抱怨模板格式不正确(g ++无论如何),这与检测违反ODR(构造函数在不同的上下文中表现不同)一样好。
答案 2 :(得分:0)
如果你在类定义中有函数的实现,则函数是内联的,不需要链接。所以没有错误。 必须有一个定义,但无关紧要,它包含在不同的cpp文件中。例如,您在头文件中定义了一个类,但在不同的cpp文件中包含头文件。