14

从我正在阅读和测试的所有内容来看,没有办法(没有预处理器宏)在共享标头中定义一个常量并确保每个 TU 没有为该常量创建自己的存储。

我可以做这个:

const int maxtt=888888;

这与以下内容完全相同:

static const int maxtt=888888;

如果此标头是共享的,它将起作用,但每个 TU 都有自己的maxtt. 我也可以这样做,以防止:

extern const int maxtt;

但是我不能maxtt在这里定义;必须在 CPP 中完成以避免链接器错误。

我的理解正确吗?

4

5 回答 5

17

由于变量是常量,因此每个 TU 都有自己的副本这一事实通常是无关紧要的。

static由于这个原因,在 C++ 中,命名空间范围内的常量是隐含的。通常这允许比只有一个带有外部链接的实例更好的代码,因为(如果变量实际上是一个常量表达式)常量通常可以直接折叠到使用站点中并且根本不需要存储.

因此,除非您真的需要获取常量的地址或类似的东西,否则您应该坚持使用静态版本。(正如您已经观察到的,您可以通过添加来强制外部链接extern。)另一个原因可能是您正在动态初始化并且只需要一次调用初始化程序:

// header:
extern int const n;

// one implementation:
int const n = init_function_with_side_effects();

静态构造(int const n = init();在标头中)将导致函数在每个 TU 中调用一次。

于 2013-01-20T23:00:29.443 回答
9

你写,

“从我正在阅读和测试的所有内容来看,没有办法(没有预处理器宏)在共享标头中定义常量并确保每个 TU 不会为该常量创建自己的存储。”

幸好这是不正确的。

对于一个小的整数值,您始终可以只使用enum. 权衡是你不能传递一个enum值的地址,因为它没有地址。这是纯粹的价值。

但是,为整数值节省空间是一件毫无意义的事情,因为它太小了。

所以,让我们考虑一件大事,

struct BiggyThingy
{
    unsigned char zeroes[1000000];
    BiggyThingy(): zeroes() {}
};

现在我们如何BiggyThingy在头文件中声明一个常量并确保整个程序只有一个常量?

使用内联函数。

那么最简单的就是这个:

inline BiggyThingy const& getBiggyThingy()
{
    static BiggyThingy const theThingy;
    return theThingy;
}

static BiggyThingy const& biggyThingy = getBiggyThingy();

如果您不希望引用在每个翻译单元中占用空间(如指针),则只需使用没有符号简化引用的函数。

使用模板常量技巧。

这是另一种提供常量的方法,而是利用模板的特殊规则:

template< class Dummy >
class BiggyThingyConstant_
{
public:
    static BiggyThingy const value;
};

template< class Dummy >
BiggyThingy const BiggyThingyConstant_<Dummy>::value;

typedef BiggyThingyConstant_<void> BiggyThingyConstant;

可以像这样访问

foo( BiggyThingyConstant::value )

或者,如果您想要更好的符号,您可以为每个翻译单元添加一个参考,就像内联函数解决方案一样。

免责声明:

编译器未修改的代码。

但我想你明白了。;-)

于 2013-01-20T23:30:32.640 回答
6

仅当您应用任何需要常量地址的操作时,此代码才会在 TU 中生成一个常量。

static int maxtt = 888888;
int * pmaxtt = &maxtt; //address of constant requested.

maxtt这也可以正常工作并避免链接器问题(尽管如果请求地址,它将存储在每个 TU 中):

constexpr int maxtt = 888888;

避免extern构造,因为它无法优化。

于 2013-01-20T23:00:15.763 回答
4

如果您非常担心存储,请使用枚举:

enum { maxtt = 888888 };

枚举器是标量右值,因此不需要存储。说是违法的&maxtt

于 2013-01-20T23:06:01.183 回答
0

确实,您对语义的理解是正确的。

在实践中,每个翻译单元可能无法获得整数存储的副本。一个原因是编译器可能将值实现为文字,无论它被引用到哪里。如果链接器发现它没有被引用,它也可能足够聪明地丢弃存储。

编译器可能无法自由地为您的常量使用文字。您可能会引用该整数,或获取指向它的指针。在这种情况下,您需要存储——甚至可能需要交叉编译的唯一性。如果您const在每个编译单元中获取符号的地址,您可能会发现它不同,因为每个对象都会获得一个唯一的静态副本。

如果您使用枚举,您可能会遇到类似的问题;您const int有存储空间,您可以获取该存储空间的地址。在将枚举数存储在某个地方之前,您无法获取枚举数的地址。

于 2013-01-20T23:05:38.850 回答