【问题标题】:How can a type that is used only in one compilation unit, violate the One Definition Rule?仅在一个编译单元中使用的类型如何违反单一定义规则?
【发布时间】:2010-08-18 14:59:49
【问题描述】:

有人告诉我,这些类型在它们自己的唯一翻译单元中可见,违反了单一定义规则。有人可以解释一下吗?

//File1.cpp
#include "StdAfx.h"
static struct S { int Value() { return 1; } } s1;
int GetValue1() { return s1.Value(); }

//File2.cpp
#include "StdAfx.h"
static struct S { int Value() { return 2; } } s2;
int GetValue2() { return s2.Value(); }

// main.cpp
#include "stdafx.h"
extern int GetValue1();
extern int GetValue2();
int _tmain(int argc, _TCHAR* argv[])
{
    if( GetValue1() != 1 ) throw "ODR violation";
    if( GetValue2() != 2 ) throw "ODR violation";
    return 0;
} 

我知道如何解决问题。根据标题,我正在寻找为什么它违反了 ODR。它如何违反:“在任何翻译单元中,模板、类型、函数或对象只能有一个定义。”?或者它可能违反了规则的不同部分。

【问题讨论】:

  • 您违反了规则的另一部分。我已经用详细信息更新了我的答案。

标签: c++


【解决方案1】:

问题是,虽然s1s2只有内部链接,但S的对应定义都有外部链接。

您要做的是使用匿名命名空间:

//File1.cpp
#include "StdAfx.h"
namespace {
    struct S { int Value() { return 1; } } s1;
}
int GetValue1() { return s1.Value(); }

//File2.cpp
#include "StdAfx.h"
namespace {
    struct S { int Value() { return 2; } } s2;
}
int GetValue2() { return s2.Value(); }

编辑:

匿名命名空间中的所有内容,包括类定义,都有内部链接。

匿名命名空间中的定义仍然具有外部链接,但编译器确保它们接收到不会与来自其他翻译单元的任何定义冲突的唯一名称。

【讨论】:

  • 完全不正确。位于未命名的命名空间内对任何链接都没有影响。
  • 我知道如何解决这个问题。我正在寻找为什么这是违规行为。它如何违反:“在任何翻译单元中,模板、类型、函数或对象只能有一个定义。”?或者它可能违反了规则的不同部分。
  • 未命名的命名空间不会影响链接,它只是将内容放在与任何其他命名空间不同的命名空间中。他们仍然有外部联系。
  • @Charles 和@Mike:谢谢——不知道。我会相应地更新答案。
  • @jyoung:这是您违反的 ODR 的一部分:“有些东西,例如类型、模板和外部内联函数,可以在多个翻译单元中定义。对于一个给定实体,每个定义必须相同。”
【解决方案2】:

这是不安全的,因为您有两个名为 S 的结构。 static 关键字只适用于变量声明;相当于你写了:

struct S {
    int Value() {return 1;}
};

static S s1;

编译器在编译时不会注意到这一点,因为它分别处理每个翻译单元。结构中的Value 函数被修改为完全相同的名称,并成为目标文件中的弱全局符号,因此链接器不会抛出有关符号名称冲突的错误;它只是选择一个在完全链接的二进制文件中使用。这可能是第一个符号定义,这意味着您实际上可以根据链接对象的顺序获得不同的行为:

> g++ -o test test.o test1.o test2.o && ./test
s1 is 1
s2 is 1

> g++ -o test test.o test2.o test1.o && ./test
s1 is 2
s2 is 2

您可以通过将结构包装在匿名命名空间中来解决此问题(这将使Value 函数符号成为局部变量而不是弱全局变量):

namespace {
    struct S {
        int Value() {return 1;}
    } s1;
}

或者只是删除结构的名称,因为您实际上并不需要它:

struct {
    int Value() {return 1;}
} s1;

【讨论】:

  • "只是删除结构的名称..." 我不知道在声明结构时可以不命名结构。我认为上课也可以吗?
  • @Stephane class 是可能的,但这种用法极为罕见。命名结构本质上只是对它进行类型定义,因此您可以稍后使用名称来引用整个结构,但您可以每次都重新定义匿名结构。例如:struct {int x;} foo() {struct {int x;} rtn; rtn.x = 4; return rtn;} 将声明一个函数foo,它返回一个结构,你可以用struct {int x;} var = foo(); 调用它
  • 也许使用非常罕见,但现在我知道这是可能的。感谢这个例子。
【解决方案3】:

您在全局命名空间中以两种不同的方式定义了struct S,这违反了单一定义规则。特别是,::S::Value() 有两种不同的定义,实际上最终会被调用的是未定义的。

您应该使用无名命名空间来确保在每个翻译单元中定义一个明确命名的 struct S 版本:

namespace { struct S {int Value() {return 1;}} s1; }
int GetValue1() {return s1.Value();}

单一定义规则比您引用的第一段要多得多。最后一段基本上是说有些东西,包括类定义,可以在一个程序中出现不止一次,只要它们都是相同的。您的代码打破了最后一个条件。或者,用标准的(删节的)话来说:

如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中可以有多个类类型的定义......给定这样一个名为 D 的实体在多个翻译单元中定义,那么 D 的每个定义都应由相同的标记序列组成。

【讨论】:

  • 从技术上讲,有两个 ::S::Value() 定义是允许的例外,因为两者都(隐式)声明为内联(inline 函数可以在多个 TU 中定义并且必须定义在每个使用它们的 TU 中)。不允许的是定义不相同。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 2018-11-21
  • 2016-04-05
  • 1970-01-01
相关资源
最近更新 更多