是什么决定了在两个源文件中为同名的类包含哪个类定义?

时间:2012-11-14 22:32:36

标签: c++ templates

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

例如:

// 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的常见场景。每个源文件必须具有相同的实例化模板类函数的单独编译版本,因此链接器必须在链接时选择这些函数的不同此类编译版本,我想。

- ADDENDUM以及有关Java的相关问题 -

我注意到各种答案都表明了C ++的单一定义规则(http://en.wikipedia.org/wiki/One_definition_rule)。作为一个有趣的方面,我是否正确Java没有SUCH规则 - 因此Java规范允许Java中允许多个不同的定义?

5 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:2)

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

最终,在您的示例中,由您的编译器使用。

答案 2 :(得分:2)

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

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

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

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

这两个类不会发生冲突。

答案 3 :(得分:1)

如果你允许,编译器会给你一个多个定义的警告(你应该这样做)。

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

答案 4 :(得分:1)

One Definition Rule存在的原因是使用哪个定义并不重要,它们都是相同的。完全由编译器和链接器决定使用哪个版本,或者它们是否一致。唯一可见的外部副作用是当函数内部存在静态变量时,必须在函数的所有实例之间使用变量的单个实例。

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