【问题标题】:Enumeration values versus static constants in metaprogramming元编程中的枚举值与静态常量
【发布时间】:2021-04-29 22:05:29
【问题描述】:

我正在阅读 C++ 模板:完整指南,第 23 章。元编程。最后,它描述了 在元编程中使用枚举值与静态常量的区别。考虑以下两种计算 3 的 N 次方的实现:

枚举实现:

// primary template to compute 3 to the Nth
template<int N>
struct Pow3 {
   enum { value = 3 * Pow3<N-1>::value };
};

// full specialization to end the recursion
template<>
struct Pow3<0> {
   enum { value = 1 };
};

静态常量实现:

// primary template to compute 3 to the Nth
template<int N>
struct Pow3 {
   static int const value = 3 * Pow3<N-1>::value;
};
// full specialization to end the recursion
template<>
struct Pow3<0> {
   static int const value = 1;
};

紧接着它说后一个版本有一个缺点。如果我们有一个函数 void foo(int const&amp;); 并将元程序的结果传递给它:foo(Pow3&lt;7&gt;::value); 书中说:

编译器必须传递 Pow3::value 的地址,这会强制 编译器实例化和分配静态成员的定义。因此, 计算不再局限于纯粹的“编译时”效果。枚举值不是左值(即它们没有地址)。所以,当我们 通过引用传递它们,不使用静态内存。几乎就像你通过了 计算值作为文字。

说实话,我完全不明白他们的解释。我认为在静态常量版本中,我们在编译时评估结果,但实际引用发生在运行时,因为我们需要传递地址。但是我看不到前一种情况(枚举实现)有太大区别,因为我们应该在那里有临时物化(prvalue -> xvalue 转换),并且由于临时对象也有地址,所以我的思考过程失败了。它还说“不使用静态内存”,我也不明白,因为静态内存应该指的是在编译时发生的分配。

有人可以更详细地解释整个事情吗?

【问题讨论】:

  • 静态成员具有链接时效果:您将获得变量的符号,链接器必须处理它。另一方面,临时变量与链接器完全无关。
  • 从另一个角度来看,对象在其生命周期中通常具有不同的地址。静态变量具有静态存储持续时间(= 进程/库的生命周期)。因此,它们的生命周期由可执行文件中的 .. 静态条目维护(如果它们没有被优化删除)。

标签: c++ template-meta-programming


【解决方案1】:

(枚举实现)因为我们应该在那里有临时物化(prvalue -> xvalue 转换)并且因为临时对象也有地址

确实如此,但临时 xvalue 仅在函数调用期间存在。这与向函数传递整数文字相同。因此,可寻址对象在运行时临时存在,具有自动作用域。

反之,

... 强制编译器实例化和分配静态成员的定义。因此,计算不再局限于纯粹的“编译时”效果。

static const int 是一个具有static 存储持续时间的对象。不需要临时实现,它是一个对象,它在您的程序启动时就存在,并且您正在引用它。

如果您编写多个 .cpp 文件,包括带有 Pow3 的标头,并且它们都调用 foo(Pow3&lt;3&gt;)

  • enum 版本将在每个翻译单元中发出类似 foo(27) 的内容,并带有三个不相关的临时 xvalues
  • static 版本将发出一个等效于 const int Pow3&lt;3&gt;::value = 27; 的全局对象,并且每个翻译单元将引用(即引用)同一个对象

【讨论】:

  • 所以你是说最好只在一段时间内使用一块内存而不是在整个执行过程中使用它?现在这很有意义。但我仍然对“......不再局限于纯粹的'编译时'效果”感到困惑。因为这两种情况都涉及运行时效应。
  • 更公平地说,prvalue 版本只影响可执行代码,而静态版本影响代码和数据。我也不是说它更好,只是不同:最显着的影响可能是在符号表上。
【解决方案2】:

我建议这样看:
类型、类型的实例和该类型可以采用的值之间存在差异。

在第一种情况下,您指定的类型(未命名的枚举)只有一个可能的值 value(这是编译时常量)。
没有该类型的实例化,因此不会在编译时或运行时使用内存。
每次代码引用Pow3&lt;N&gt;::value时,编译器不会创建该类型的实例,而是直接使用常量。

在第二种情况下,您改为指定一个 int 类型的变量 value,并将编译时常量分配给它。
这个变量存在,所以会占用内存。
每次代码引用Pow3&lt;N&gt;::value时,编译器都会使用该变量。

【讨论】:

  • 我相信我明白你的意思,这在一定程度上是有道理的。但是我没有看到您在任何地方提到过引用,我觉得正如书中指出的那样,这是必要的。如果签名是void foo(int const);是否需要分配?
  • @JamesGroon 对我来说,这句话在使用“通过引用”一词时似乎有点误导。非 const 引用必须指向现有的东西,因此枚举 value 只能通过值或 const-reference 传递(创建临时实例化)。对于void foo(int),编译器可能仍会分配value 变量,但它可能会在稍后的步骤中被优化掉(在这些情况下,优化几乎是通配符)。
  • 我同意。只有一件事让我感到烦恼,我向那个名叫无用的家伙提出了同样的问题。这本书说“......不再局限于纯粹的'编译时'效果。”,但我不明白这背后的含义,因为枚举版本也涉及运行时效果。但这种效果是可取的,因为内存在函数执行结束时释放,而不是在使用静态 const 版本时释放。
猜你喜欢
  • 1970-01-01
  • 2011-01-14
  • 1970-01-01
  • 2014-06-01
  • 1970-01-01
  • 2015-04-26
  • 1970-01-01
  • 2014-06-29
  • 1970-01-01
相关资源
最近更新 更多