C++外部常量int用于数组大小

5
我有以下三个文件在我的代码中(大部分代码已经删除,仅用于隔离问题)。
global.h:
//global.h
#ifndef GLOBAL_H
#define GLOBAL_H
extern const int ARRAYSIZEX;
extern const int ARRAYSIZEY;
extern const int ARRAYSIZEZ;
#endif //GLOBAL_H

global.cpp:

//global.cpp
#include "global.h"
const int ARRAYSIZEX = 5;
const int ARRAYSIZEY = 2;
const int ARRAYSIZEZ = 4;

主要的:
//main
#include "global.h"
using namespace std;

someType mySomeTypeArray[ARRAYSIZEX][ARRAYSIZEY][ARRAYSIZEZ];

int main(int argc, char **argv)
{
//...
}

编译时在声明mySomeTypeArray时出现了三个错误。
错误信息如下:
error:数组边界在']'标记之前不是整数常量
我想保持我的全局变量和数组大小定义在global.h/cpp中,仅仅是为了组织,这样所有的配置参数都在一个地方。请问如何正确实现我想要做的事情?
谢谢。

问题在于你的 main.cpp 只看到了 extern 声明,而没有实际赋值给一个值,因此它不知道要创建多大的数组。 - xaxxon
如果这是 C++,那么这些固定长度的数组怎么了?为什么不使用 std::vector 呢? - tadman
std::vector的行为与数组非常不同。有很多理由不使用vector。 - xaxxon
@xaxxon 感谢您的输入!如果您想将该任务分配也提供给其他 .cpp 文件,您会如何构建这样的应用程序结构? - boxcartenant
使用全大写编译器标识符来表示常量是反模式。坏习惯难改。 - Slava
5个回答

4
问题出在这里,extern int x的意思是“x在另一个文件中被定义了,但不用担心细节,你只需要知道它是一个int” 。通常这已经足够好了,但是当编译器需要立即知道x时就不行了。
由于定义在另一个文件中,所以无法知道。必须先编译那个文件才知道,由于C ++的工作方式,该编译的结果不能影响此文件的编译。
如果要共享这些值,则需要在头文件中将其声明为const intextern int不起作用。
虽然这只是一个微不足道的例子,但根本没有理由要使用extern。只需像常规const int一样在头文件中定义这些值即可。

3
你的声明失败了,因为数组大小需要在编译时评估,而你的封装方案实际上将值从编译器隐藏起来。这是因为编译器只适用于单个翻译单元。在编译main.cpp时,由于include语句,你的编译器只看到extern const int ARRAYSIZEX,但不看到在另一个翻译单元中可见的值,所以它无法确定内存布局。
虽然在某些情况下可以使用const变量作为数组大小,但编程语言提供了更合适的constexpr修饰符,它带有一组限制,强制其在编译时评估并适用于数组大小。我建议在适当的情况下始终使用它,因为它会指出像这样的错误。在这种情况下,你将得到编译器错误,因为extern constexpr声明是不合法的,这提示了正确的解决方案:直接在头文件中保存编译时常量的值。 global.h
constexpr int ARRAYSIZEX = ...;
constexpr int ARRAYSIZEY = ...;
constexpr int ARRAYSIZEZ = ...;

main.cpp

#include "global.h"
someType mySomeTypeArray[ARRAYSIZEX][ARRAYSIZEY][ARRAYSIZEZ];

我尝试了一下,对于简单的情况可以工作。这是否也会给我外部链接,以便我可以在多个文件中访问常量? - boxcartenant
1
@boxcartenant,您不需要外部链接即可在多个文件中使用常量。 - M.M
@boxcartenant constexpr变量隐式地是inline的,因此它们不受一个定义规则的限制。只要定义它们的每个翻译单元使用完全相同的定义,就可以了。简而言之,请确保您在一个头文件中定义它们,并将其包含在尽可能多的文件中。 - patatahooligan
1
constexpr变量是静态成员时,它们会隐式地内联。目前这将导致ODR违规。 - Passer By
我的错误,变量只会有内部链接。 - Passer By
显示剩余2条评论

2

数组大小必须由整数常量表达式指定。如果一个const int对象被声明为初始化器,并且该初始化器也是一个整数常量表达式,则可以在整数常量表达式中使用它。你的ARRAYSIZE...变量不满足该要求,在main中它们被声明时没有初始化器。你不能在main中使用ARRAYSIZE...变量作为数组大小。

除非你有特定的要求需要给这些变量提供外部链接,否则只需在头文件中声明(和定义)它们:

const int ARRAYSIZEX = 5;
const int ARRAYSIZEY = 2;
const int ARRAYSIZEZ = 4;

这些对象将具有内部链接,这与您原始变量的尝试不同。

如果确实想要给它们外部链接,请在头文件中声明它们为inline extern const

inline extern const int ARRAYSIZEX = 5;
inline extern const int ARRAYSIZEY = 2;
inline extern const int ARRAYSIZEZ = 4;

由于单独使用inline可以防止const强加内部链接,所以在这些声明中extern是完全可选的。另外,由于inline const组合可以被constexpr替换(正如@M.M在评论中指出的),因此你只需使用constexpr就能实现相同的效果。

constexpr int ARRAYSIZEX = 5;
constexpr int ARRAYSIZEY = 2;
constexpr int ARRAYSIZEZ = 4;

我尝试了这个方法,它起作用了...但是我认为我不理解extern和inline的组合。我以为"extern"意味着在这里定义并在另一个文件中实例化一次,但是"inline"不是意味着它必须在每个翻译单元中重新定义吗?(而我的理解是每个翻译单元是一个单独的文件)。 - boxcartenant
constexpr could replace inline const - M.M
2
@boxcartenant:不,extern的意思是“具有外部链接性”。它需要覆盖由const强制实施的内部链接性。它是否意味着“在另一个文件中实例化”或“在此处实例化”取决于初始化程序的存在。如果存在初始化程序,则extern表示“在此处实例化并可供其他人使用”。inline则是另一回事。它的意思是“尽管具有外部链接性,在不同的翻译单元中定义它多次是可以的”,这正是我们通过在头文件中定义它所做的。 - AnT stands with Russia
@AnT 很好地总结了这些函数。我非常感谢你的回答!通过研究这些术语,我学到了很多。 - boxcartenant
1
@boxcartenant:更正一下:在这种情况下,inline已经足够防止const强加内部链接。这意味着在那里不需要指定extern。这不是一个错误,但是没有它也可以工作。 - AnT stands with Russia

1
尽管其他答案和评论中已经讨论了inlineconstexpr,但有一个值得强调的方面。以下头文件将以高效且易于理解的方式满足OP的需求。
//global.h
#ifndef GLOBAL_H
#define GLOBAL_H
inline constexpr int ARRAYSIZEX = 5;
inline constexpr int ARRAYSIZEY = 2;
inline constexpr int ARRAYSIZEZ = 4;
#endif //GLOBAL_H

这是基于内联变量(在C++17中引入)的工作原理。这些内联变量允许在每个使用它们的翻译单元中定义一次,只要定义相同即可。因此,它们可以方便地在多个源文件中包含的头文件中定义。此外,当未额外声明为静态时,这些内联变量具有外部链接。因此,这些变量在每个翻译单元中具有相同的地址,使得这种方法高效。

此外,还有其他 线程 可以在SO上探索,以进一步了解此主题。


0
问题在于ARRAYSIZEXARRAYSIZEYARRAYSIZEZ 不是编译时常量。它们是常量,因此它们的值不能更改,但编译器不知道它们的值。
在C++中,编译过程由三个基本步骤组成。
1. 预处理所有源文件由预处理器完成。 2. 编译每个翻译单元(.cpp文件)由编译器完成。对于每个翻译单元编译器创建一个对象文件。 3. 把所有对象文件链接起来由链接器完成。输出是可执行文件。
在C++中,关键字extern对于编译器意味着变量“在某处”定义。编译器不知道变量的真实地址,但通过放置关键字extern可以确保变量确实存在,并且链接器将能够通过名称找到它的地址,从而在创建可执行文件时解决该问题。
问题在于,编译器在第二步中想要创建对象文件,但是它不知道数组的大小将会是多少,因为它不知道这些常量的值。是的,在第三步中,当链接器把所有对象文件组合在一起时,最终将找到它们,但对于编译器来说已经太晚了。所以它会生成错误。
解决方案很简单。使用已经提到的constexpr关键字,并使用初始化器初始化所有变量。关键字constexpr标记编译时常量-必须在初始化器中初始化并且编译器已知的常量。

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