标题中的字符串 - 这是否违反了ODR?

时间:2016-06-18 21:13:52

标签: c++ c++11 c++14 language-lawyer one-definition-rule

考虑以下带有两个编译单元的程序。

// a.hpp

class A {
  static const char * get() { return "foo"; }
};

void f();
// a.cpp

#include "a.hpp"
#include <iostream>

void f() {
  std::cout << A::get() << std::endl;
}
// main.cpp

#include "a.hpp"
#include <iostream>

void g() {
  std::cout << A::get() << std::endl;
}

int main() {
  f();
  g();
}

由于某种原因需要创建全局字符串常量是很常见的。以完全天真的方式执行此操作会导致链接器问题。通常,人们在标题中放置一个声明,在单个编译单元中放置一个定义,或者使用宏。

我的印象是,使用函数这样做(如上所示)是“好的”,因为它是一个inline函数,并且链接器消除了生成的任何重复副本和程序使用这种模式编写似乎工作正常。但是,现在我怀疑它是否真的合法。

函数A::get在两个不同的翻译单元中使用了odr,但它是隐式内联的,因为它是一个类成员。

[basic.def.odr.6]中声明:

  

可以有多个内联函数的定义   外部联动(7.1.2)......在一个程序中提供了每个定义   出现在不同的翻译单元中,并且定义满足以下要求。特定   这样一个名为D的实体在多个翻译单元中定义,然后是    - D的每个定义应由相同的令牌序列组成;和
   - 在D的每个定义中,根据3.4查找的相应名称应指定义的实体   在D的定义内,或在重载决议(13.3)之后和之后应引用同一实体   部分模板特化(14.8.3)的匹配,但名称可以引用非易失性   如果对象在D的所有定义中具有相同的文字类型,则具有内部链接或无链接的const对象,   并且使用常量表达式(5.19)初始化对象,并且该对象不使用odr,并且   对象在D的所有定义中具有相同的值;和
   - 在D的每个定义中,相应的实体应具有相同的语言链接;和
   - ......(更多条件似乎不相关)

     

如果D的定义满足所有这些要求,   然后程序的行为应该像D的单一定义一样。如果D的定义不满足   这些要求,然后行为是未定义的。

在我的示例程序中,两个定义(每个翻译单元中的一个)分别对应于相同的标记序列。 (这就是我原本以为没关系的原因。)

但是,不清楚第二个条件是否满足。因为,名称"foo"可能与两个编译单元中的同一个对象不对应 - 它们可能是每个中的“不同”字符串文字,不是吗?

我尝试更改程序:

  static const void * get() { return static_cast<const void*>("foo"); }

这样它打印字符串文字的地址,我得到相同的地址,但是我不确定这是否可以保证发生。

是否属于“......应指代D定义中定义的实体”? "foo"是否被认为是A::getconst char[]内定义的?它可能看起来如此,但正如我非正式地理解的那样,字符串文字最终会导致编译器发出某种全局A::get,它存在于可执行文件的特殊段中。这个“实体”是否被认为是"foo"或者是不相关的?

[basic][3.4]是否被视为“名称”,或者“名称”一词仅指有效的C ++“标识符”,可以用于变量或函数吗?一方面它说:

  

[lex.name][2.11]
  名称是使用标识符(2.11),operator-function-id(13.5),literal-operator-id(13.5.8),转换 -   function-id(12.3.2)或template-id(14.2),表示实体或标签(6.6.4,6.1)。

,标识符为

  

[expr.prim.general][5.1.1.1]
  标识符是一个任意长的字母和数字序列。

所以看起来字符串文字不是名字。

另一方面,第5节

  

lvalues
  字符串文字是左值;所有其他   文字是prvalues。

一般来说,我认为{{1}}有名字。

1 个答案:

答案 0 :(得分:7)

你的最后一个论点是无稽之谈。 "foo"甚至不是语法上的名称,而是 string-literal 。字符串文字是左值,一些左值有名称并不意味着字符串文字是或有名字。代码中使用的字符串文字不会违反ODR。

实际上,在C ++ 11之前,要求跨TU的多个内联函数定义中的字符串文字指定相同的实体,但CWG 1823删除了多余且大部分未实现的规则。

  

因为,名称"foo"可能与...中的同一对象不对应   两个编译单元 - 它可能是一个&#34;不同的&#34;字符串字面量   在每一个,没有?

正确,但那是无关紧要的。因为ODR不关心特定的参数值。如果你确实设法得到一个不同的例如要在两个TU中调用的函数模板特化,这将是有问题的,但幸运的是字符串文字是无效的模板参数,所以你必须要聪明。

相关问题