在头文件中使用`const char thing[] = "foo";`吗?

5
我想创建一个常量全局字符数组,满足以下条件:
  • 可以在多个翻译单元中使用。
  • 数组的长度由用于初始化它的字符串字面值推导出。
  • 该字符串字面值在我的源代码(如果可能,也在目标文件中)中只存在一次。
  • 所有翻译单元在编译时都能访问到长度。
  • 当链接多个翻译单元时没有ODR冲突。
理论上,通过使用const char[]并以强制数据/符号进入COMDAT节的方式将声明/定义放置在头文件中,应该是可能的,但我不知道标准(甚至任何编译器)是否支持这样做。
注:假设将使用任何惯用语法用于数百到数千个常量,并跨越许多文件。
编辑:我知道的“最干净”的解决方案如下,符合上述所有要求:
template<bool> struct data_ {
  static const char kFoo[];
};

template<> const char data_<true>::kFoo[] = "bar\0other\0stuff\0";
typedef data_<true> data;


#include <stdio.h>

template<typename T, int N>
void Print(T(&var)[N]) { printf("%d %s\n", N, var); }

int main() { Print(data::kFoo); return 0; }

这仍然相当丑陋。


另一方面,如果我只放弃3b(保证相同的存储模块内联),那么这份工作将得以实现:

const char kFoo[] = "bar\0other\0stuff\0";

因为默认情况下它具有内部链接。一个好的链接器可以合并这些,但此时您不能说关于地址/标识符相等性之间的关系(即不要将其转换为指针并将其用作标识)。但这是一个警告,几乎在大多数情况下都是无害的。

因为默认情况下它具有内部链接。一个好的链接器可以合并这些,但此时您不能说关于地址/标识符相等性之间的关系(即不要将其转换为指针并将其用作标识)。但这是一个警告,几乎在大多数情况下都是无害的。

3
在头文件中,这样的变量(const 但不是 extern)将获得内部链接,意味着每个翻译单元中都会有一个不同的对象。 - dyp
1
我认为您可以使用类模板的静态数据成员或内联函数中的静态局部变量来满足这些要求。 - dyp
另一个有趣的想法是将字符串字面地址和长度分别存储,并让链接器合并这些字符串字面量。 - dyp
const而不是extern无法通过3b点。模板解决方案很丑陋,需要重复名称和大部分类型。函数静态解决方案需要自动返回类型(C++14,尚未实现),否则会失败4点。我不确定如何将单独的存储空间应用于所有点的2、3a和4。 - BCS
1
虽然我通常不赞成使用单例模式,但是创建一个封装字符串字面量的单例类应该能够满足您的所有要求,除了可能有一些限制,比如第四个要求:“所有翻译单元都可以在编译时访问长度”,但仍然可能有一些方法来强制执行。 - YoungJohn
显示剩余2条评论
3个回答

2

标题:

#define LITERAL "Hello, world"
extern char const literal[sizeof LITERAL];

一个源文件:

char const literal[] = LITERAL;

仍然不能保证任何特定的编译器/链接器只会生成一个字符串文字的副本(但它确保了&literal[0]在所有单元中都是相同的要求)。


宏和跨文件或模板的重复标识符以及单个文件中的重复标识符... :-( - BCS
@BCS 嗯?这满足了您列出的所有条件,但我不知道您所说的“单个文件中的重复标识符”是什么意思(这也不是您列出的条件之一)。 - M.M
模板解决方案(包含在问题中)中声明的标识符出现在样板文件的多个位置(您的解决方案也是如此),但它们都可以放在一个单独的文件中。简而言之,我现在知道几种使其工作的方法,但它们都很丑陋。 - BCS
我的代码中只有两处标识符(一个.h文件和一个.cpp文件)。你可以使用预处理技巧将其减少到一个位置,但你没有列出它作为要求,我不知道你认为这是否重要。 - M.M
你的代码确实符合我所述的所有要求。但是,在我看来,它仍然不够优雅。或者换句话说:我不喜欢它,也不喜欢其他选项。如果不需要更改语言,很可能我都不会喜欢任何东西。 - BCS
显示剩余2条评论

1
自C++17以来,您可以在头文件中编写以下内容:
inline char const thing[] = "foo";

符合所有标准。

注意:除非显式声明为static,否则inline变量具有外部链接性。关于const变量默认仅适用于非内联变量的内部链接规则。


你仍然需要extern,因为这是一个变量而不是函数(否则链接器可能无法合并它们)。 - Daniel H
@DanielH 我代码中的 thing 已经具有外部链接性 (见 [basic.link]/4.1) - M.M
你说得对。我记得有类似的内容,但找不到参考资料,所以以为自己记错了。结果发现就在我查找的下面。 - Daniel H

-3

你不想要吗

const char * const LITERAL="foo";

不,我认为这要么违反了ODR,要么导致在二进制文件中存储了多个“foo”。此外,该代码无法在编译时获取数组的大小。(考虑字符串是否包含\0或长度是否用作模板参数。) - BCS
1
没有ODR违规,LITERAL具有内部链接。 - CB Bailey
2
除了一个问题,前提是工具链已正确配置以合并重复的字符串文字(这是一种不常见的配置选项),所有标记都符合要求。缺少的一个问题是:从字符串文字中推导出数组的长度。也就是说,应用 sizeof(LITERAL) 不会得到 OP 寻求的结果。 - WhozCraig
为了增加难度,我要说“仅一次”要求是不同单元的字符串之间的指针比较必须定义良好且相等(符合规范的编译器需要确保这一点)。 - BCS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接