2

如果我在一个项目中有两个源文件,每个源文件都定义了一个同名的类,那么是什么决定了使用哪个版本的类?

例如:

// file1.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(1) {}
    int a;
};

int main()
{
    // foo() <-- uncomment this line to draw in file2.cpp's use of class A

    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

...

//file2.h:

void foo();

...

// file2.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(2) {}
    int a;
};

void foo()
{
    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

我已经能够A使用相同的代码获得链接器选择的不同版本 - 只需更改我键入代码的顺序(沿途构建)。

诚然,在同一个名称空间中包含具有相同名称的类的不同定义是一种糟糕的编程习惯。但是,是否有定义的规则来确定链接器将选择哪个类 - 如果是,它们是什么?

作为这个问题的一个有用的附录,我想知道(通常)编译器/链接器如何处理类 - 编译器在构建每个源文件时是否将类名和编译的类定义合并到目标文件中,而链接器(在名称冲突的情况下)丢弃一组编译的类函数/成员定义?

名称冲突的问题并不神秘 - 我现在意识到它每次都会发生一个仅包含头文件的模板文件#included由两个或多个源文件(随后实例化相同的模板类,并调用相同的成员函数,在这些多个源文件),这是 STL 的常见场景。我认为,每个源文件都必须具有相同实例化模板类函数的单独编译版本,因此链接器必须在链接时在这些函数的不同此类编译版本中进行选择)。

-- 附有 Java 相关问题的附录 --

我注意到各种答案都表明了 C++ 的单一定义规则 ( http://en.wikipedia.org/wiki/One_definition_rule )。顺便说一句,我是否正确地认为 Java 没有这样的规则 - 因此 Java 规范允许在 Java 中使用多个不同的定义?

4

5 回答 5

3

这样的程序违反了单一定义规则并表现出未定义的行为。

如果程序中有多个类或内联函数定义(在不同的翻译单元或源文件中),则所有定义必须相同。编译器和链接器都不需要诊断所有违反此规则的行为(并非所有违规行为都可以轻松诊断)。

于 2012-11-14T22:33:49.523 回答
2

如果一个 C++ 程序提供了同一个类的两个定义(即,在同一个命名空间中并且命名相同),那么该程序就违反了标准的规则并且你会得到未定义的行为。究竟会发生什么在某种程度上取决于编译器和链接器:有时您会收到链接器错误,但这不是必需的。

明显的解决方法是不要有冲突的类名。获得唯一类名的最简单方法是在未命名的命名空间中定义本地使用的类型:

// file1.cpp
namespace {
    class A { /*...*/ };
}

// file2.cpp
namespace {
    class A { /*...*/ };
}

这两个类不会冲突。

于 2012-11-14T22:37:57.067 回答
2

这只是成功链接,因为 2 个构造函数的定义隐含为inline。尝试将它们移到类下而不使用inline关键字。您正在滥用的链接类型告诉链接器将有多个定义,通常会错误地认为您正在破坏一个定义规则,而您实际上正在破坏该规则。通常,这种允许您看似破坏 ODR 的条件存在于模板之类的东西中,模板在不同的翻译单元中总是有多个相同的定义。但这就是条件:不同翻译单元中的定义必须相同。

最终,在您的示例中,使用哪个编译器取决于您的编译器。

于 2012-11-14T22:36:49.027 回答
1

如果您允许(您应该这样做),编译器会针对多个定义向您发出警告。

gnu 链接器按照您在命令行上显示文件的顺序解析符号,因此它使用它看到的第一个定义。不确定所有链接器是否都以相同的方式工作。

于 2012-11-14T22:37:52.553 回答
1

存在单一定义规则的原因是无论使用哪个定义都无关紧要,它们都是相同的。至于使用哪个版本,或者它们是否一致,完全取决于所讨论的编译器和链接器。唯一外部可见的副作用是当函数内部有一个静态变量时,必须在函数的所有实例之间使用该变量的单个实例。

通过违反单一定义规则,您以与正确编写的程序无关的方式暴露了编译器/链接器的机制。

于 2012-11-14T22:45:48.077 回答