【问题标题】:C++: Are classes/structs ever initialized at compile-time?C++:类/结构是否在编译时初始化?
【发布时间】:2012-05-30 16:14:11
【问题描述】:

假设我用构造函数Foo(int) 编写了一个类Foo。 我有这段代码:

Foo a(i), b = a + Foo(2);

如果我在代码中使用常量调用构造函数,例如Foo(2),编译器是运行一次并存储运行时的结果,还是在运行时执行? struct/class是否只包含POD数据类型是一样的吗?

假设它在运行时执行(我相信是这种情况),有没有办法让它在编译时运行,或者具有与运行时相同的效果?

编辑:恐怕我没有说清楚。我指的是代码的Foo(2) 部分,它是完全不可变的。另外,我无法使用 C++11(我正在使用 GCC 4.1 并且无法升级),所以 constexpr 虽然有效,但不适合我。

【问题讨论】:

  • 这是在全局范围内还是在函数中?
  • 我正在考虑函数内部的情况。我担心必须多次评估Foo(2)
  • 完全取决于编译器和优化级别。允许(如果这是问题的话)。它是否会取决于 Foo 是否有副作用,它的简单程度和其他因素。为什么不把它编译成汇编看看呢。

标签: c++ class struct initialization c++03


【解决方案1】:

可以让您的a 使用constant initialization,这是静态初始化,但要做到这一点:

  1. i 多为常量表达式
  2. Foo::Foo(int) 必须是 constexpr
  3. Foo:Foo(int) 使用的任何/所有其他函数/ctor 也必须是 constexpr

b 的情况也差不多——Foo(2) 必须是 constexprFoo::operator+(Foo const &)Foo operator+(Foo const &, Foo const &)(无论你有哪个)必须是 constexpr

常量表达式的定义在 C++11 标准的 §5.19 中,以防您想更详细地研究。我的直接猜测是,如果Foo 相当简单,a 可能是可能的,但我对b 不太确定。

【讨论】:

    【解决方案2】:

    假设我用构造函数 Foo(int) 编写了一个类 Foo。我有这段代码:

    Foo a(i), b = a + Foo(2);
    

    如果我在代码中用常量调用构造函数,编译器是运行一次,然后将结果存储到运行时,还是在运行时执行?

    这有两个层次:

    • 是否可能且合法 w.r.t.完美优化器的标准——实现专家通过无限努力和天才可能做的一切——在编译时做到这一点, 和
    • 是编译时行为要求/保证标准。

    i 是编译时间常数吗?如果不是,并且传递给Foo::Foo(i)i 的值会影响其行为(无论是影响数据成员值还是日志记录等副作用),那么显然在编译时构造 Foo(i) 本质上是不可能的。如果i 是常量,它可能本质上仍然是不可能的——例如构造函数实现可能具有基于当前时间的行为,或者需要查阅其他一些运行时数据。此类问题还可能阻止Foo(2) 在编译时进行评估。

    如果i 是常量,并且Foo 的构造函数不依赖于其他仅运行时数据,则可以进行优化。但是,您的代码中没有任何内容需要 C++ 标准甚至尝试任何优化,更不用说能够优化它了。在 Foos 上调用的 + 运算符也是如此......优化可能是合法的,但肯定不是必需的。

    在实践中,我希望大多数当前主流编译器会针对编译时常量 i 优化 Foo(i) 的简单情况,但会遇到解决加法的挑战。如果您真的想知道,请在各种优化级别为您的编译器尝试它......

    假设它在运行时执行(我相信是这种情况),有没有办法让它在编译时运行,或者具有与运行时相同的效果?

    是的...你可能会从constexpr 中获得一些信息,这是 C++11 引入的一个关键字,它告诉编译器需要在编译时解析某些值(如果你 constexpr 用于变量/编译器在编译时不需要支持的值会报错)。

    其次,通常可以使用模板来表达编译时操作。为了让自己朝这个方向迈进,您可能需要搜索“C++ 模板阶乘编译时”或类似内容,以了解如何编写基本计算。

    【讨论】:

    • 正确答案。如果Fooint 的简单包装,而operator+(Foo, Foo) 只是类型安全的int+int,那么编译器甚至可以优化该位。
    【解决方案3】:

    “as-if”规则适用,即编译器可以做它喜欢做的任何事情,前提是程序的可观察行为与标准中描述的行为相同。

    如果:

    • Foo 的构造函数在 TU 中可见,
    • 析构函数~Foo也是如此,
    • 它们都没有任何副作用,
    • 他们的结果不依赖于任何需要在运行时计算出来的东西(比如时间,或者一些非常量对象的值,据我们所知,在你的代码执行时可能已经被修改了) ,
    • operator+ 是可见的,并且不会做任何事情让 RHS 的地址“逃逸”到未知代码中,或将其地址用于可观察的行为(例如将其打印出来),或做任何其他实际需要对象的事情在那里,

    然后,一个足够聪明的优化器可以完全消除 Foo(2) 临时值,并且在 operator+ 使用 RHS 的数据成员的任何地方,只要使用它知道这些成员将拥有的任何值。

    或者,作为较小的优化,它可以将值放入程序数据部分中Foo 实例的布局中,并将其用作Foo(2)。我想这就是您存储运行时结果的意思。

    这种优化是否真的发生完全是特定于实现的,并且取决于您使用的编译器和标志。反汇编代码看看到底发生了什么。

    如果您执行以下操作,您可以确保 Foo(2) 在 C++03 中只计算一次:

    static Foo foo2(2);
    Foo a(i), b = a + foo2;
    

    foo2 (根据标准)在运行时计算,即代码第一次执行。同样,编译器可以在编译时调用“as-if”规则来执行部分或全部计算,但这不是必需的。

    【讨论】:

      【解决方案4】:
      Foo a(i), b = a + Foo(2);
      

      这种初始化发生在运行时,而不是编译时。

      编译时初始化只发生在内置类型上,如果它们的初始化器可以在编译时计算,或者它们被声明为全局变量,或者static。在后两种情况下,它们在编译时被初始化为零。我在这里详细解释了这一点:

      【讨论】:

        【解决方案5】:

        已编译的代码在执行过程中可能会在不同的时间点被调用。编译代码后,Foo(2) 的值可能是不可变的。

        【讨论】:

        • 投反对票不是因为你错了,而是因为我不明白你在说什么。
        • 也错了。 Foo(2) 的值很可能取决于构造时间,因此不是编译时间常数。
        【解决方案6】:

        这发生在运行时。如果您希望它在编译时发生,那么您需要对值进行硬编码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-03
          • 1970-01-01
          • 2018-10-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多