具有相同名称的类 - 是否仅限于同一个翻译单元中?

时间:2010-05-12 05:46:42

标签: c++ class linker

我刚才有以下代码:

foo.h中

class Foo
{
    // ...
};

Foo.cpp中

#include "foo.h"
// Functions for class Foo defined here...

假设Foo内置于静态库foo.lib

现在让我说我有以下内容:

foo2.h

class Foo
{
    // ...
};

foo2.cpp

#include "foo2.h"
// Functions for class Foo defined here...

它内置于一个单独的静态库foo2.lib

现在,如果我将foo.libfoo2.lib重新关联到可执行程序foo.exe,是否应该抱怨class Foo已定义两次?

根据我的经验,编译器或链接器都没有抱怨。

我不希望编译器抱怨,因为它们已在单独的翻译单元中定义。

但为什么连接器不抱怨?

链接器如何区分Foo类的2个版本?通过装饰符号来起作用吗?

6 个答案:

答案 0 :(得分:2)

不,编译器和链接器都不需要抱怨。他们可能;但这不是必需的。
请参阅One Definition Rule

答案 1 :(得分:2)

要使此生成链接器错误,您需要强制在两个版本的foo之间共享具有外部链接的符号 - 并且必须使用这两个符号。由于来自可以具有外部链接的类的唯一符号是成员函数,因此您必须在每个具有相同名称的Foo中具有非内联函数。

实际的结果是:这可能有效,而且可能没有,并且在你尝试之前你不会知道任何给定的情况(因为你不能强制编译器内联函数)。不要这样做。

例如,在以下代码中,您将在链接时获得乘法定义的Foo::Foo

<强> foo.h中

class Foo
{
public:
    Foo();
    int a;
};

<强> foo2.h

class Foo
{
public:
   Foo();
   int b;
};

<强> Foo.cpp中

#include "foo.h"
#include <cstdio>

Foo::Foo() : a(22) {}

void do_foo()
{
    Foo foo;
    std::printf("%d\n", foo.a);
}

<强> foo2.cpp

#include "foo2.h"
#include <cstdio>

Foo::Foo() : b(33) {}

void do_foo_2()
{
    Foo foo;
    printf("%d\n", foo.b);
}

<强>的main.cpp

extern void do_foo();
extern void do_foo_2();

int main(int argc, char** argv)
{
    do_foo();
    do_foo_2();
}

答案 2 :(得分:2)

在多个翻译单元中,您可以拥有多个类类型的定义,这些定义受到一些相当强的限制,这意味着定义必须几乎相同。 (3.2 [basic.def.odr])

这也适用于枚举类型,具有外部链接的内联函数,类模板,非静态函数模板,类模板的静态数据成员,类模板的成员函数或模板特化,其中某些模板参数不是指定。

这实际上意味着您可以遵循将类定义放在头文件中并在多个翻译单元中使用它的常规做法,只要包含序列中没有任何差异会导致令牌序列或多个翻译单元之间在类定义中使用的任何名称的含义。

您不能拥有的是整个程序中类的非内联成员函数的多个定义。

违反这些规则中的任何一个会导致未定义的行为,因此不需要编译序列的任何部分来生成诊断或导致任何特定行为,因此如果您对类的两个定义稍有不同,那么我的工作或可能在运行时引起奇怪的问题。

如果你有一个同名签名类的非内联成员函数的两个定义,并且链接时会有错误,那么这不是语言保证。

值得注意的是,如果相同实体的两个定义位于库中的单独目标文件中(相同或不同的库),则您的程序可能实际上不会为给定实体包含多个定义。链接器传统上工作的方式是迭代选择目标文件,这些文件有助于解析程序中的符号,但是它们省去了无助于解析符号的目标文件。这意味着在包含定义的第一个目标文件之后,不需要包含具有替代定义的第二个目标文件。

答案 3 :(得分:1)

部分问题是每个类都在静态库中。链接器专门处理静态库,只提取必要的对象以满足具有链接的缺失符号。

是否收到链接错误将取决于foo的每个实现具有哪些符号以及它们如何打包在一起。由于您有简单的打包(每个foo包含所有方法的单个.cpp文件),因此很容易产生冲突。

所以,让我们创造一个你会发生冲突的情况。你想像这样定义一个foo:

class foo
{
public:
    foo();
    int a() const;
};

和你的第二个foo是这样的:

class foo
{
public:
    foo();
    int b() const;
};

现在每个foo都有一个相同的符号,带有默认构造函数的链接和第二个不同的符号。

现在,在您的应用程序中,在一个文件中有一个功能:

int lib1()
{
    foo f;
    return f.a();
}

以及执行此操作的第二个文件:

int lib2()
{
    foo f;
    return f.b();
}

现在,您的应用程序将有三个符号,foo::foofoo::afoo::b。由于foo::afoo::b,链接器将尝试使用两个静态库,但会导致两个不同的foo::foo副本被拉入,这将导致链接器出错。

答案 4 :(得分:0)

您必须include其中一个foo标头。编译器只会知道包含的Foo类,也许由于name mangling,链接器也可能不会抱怨。

另一方面,如果你包含两个标题,编译器应该抱怨并且链接器可能会抱怨。

答案 5 :(得分:0)

我相信编译器应该在链接器之前抱怨。让我解释一下。

  • 编译器完全按照预期处理两个独立单元的编译。
  • 当编译器获取引用“foo.h”和“foo2.h”的新文件时(根据Andreas示例),应该立即发生命名冲突。

如果两个库是分开引用的(没有源/头文件和依赖项引用两者),可能是程序由于名称重整而正确编译和链接,但我不确定。

@Igor(无法发表评论......呃!) 在您关于ODR的页面中,它与您所说的完全相反(参见第一个示例)。根据特殊规则,您可以拥有多个具有相同名称和多个定义的实体。声明的数量也取决于实体的类型。