【问题标题】:Does in class member initialization takes place at compile time or run-time?类成员初始化是在编译时还是运行时进行?
【发布时间】:2014-05-01 10:17:21
【问题描述】:

在 C++11 中引入了一个新特性,程序员可以在类定义中初始化类成员变量,见下面的代码:

struct foo
{ 
  int size = 3;
  int id   = 1;
  int type = 2;
  unsigned char data[3] = {'1', '2', '3'};
};

这个初始化是在编译期间发生的,还是这个特性只是语法糖,成员变量在默认构造函数中初始化?

【问题讨论】:

  • 不仅仅是默认构造函数。
  • 您如何设想“在编译时”发生的初始化?这对你意味着什么?
  • 如果你有一个struct foo 的全局实例,那么它很可能会在静态初始化期间“在编译时”被初始化。编译器很可能会在 .data 部分中分配变量并使用这些值对其进行初始化。因此,全局实例将在加载可执行文件时初始化。

标签: c++ c++11


【解决方案1】:

首先是的,如前所述,它是语法糖。但由于规则可能难以记住,这里有一个合乎逻辑的实验来帮助您弄清楚编译时会发生什么,什么不会发生

你的 c++11 类在类初始化器中具有特色

struct foo { int size = 3; };

还有一个课程可以帮助我们进行实验

template<int N>
struct experiment { enum { val = N }; };

让我们的假设 H0 是初始化确实发生在编译时,那么我们可以这样写

foo                a;
experiment<a.size> b;

不走运,我们无法编译。有人可能会争辩说失败是由于foo::size 是非恒定的,所以让我们试试

struct foo { const int size = 3; }; // constexpr instead of const would fail as well

再次,正如 gcc 通知我们的那样

'a' 的值不能用于常量表达式

实验b;

或者(更清楚)visual studio 2013 告诉我们

错误 C2975:“N”:“示例”的模板参数无效,预期的编译时常量表达式

因此,我们必须丢弃 H0 并推断 初始化不会在编译时发生

编译时会发生什么

有一种旧语法可以解决问题

struct foo { static const int size = 3; };

Now this compiles 但要注意这(从技术上和逻辑上)不再是在类中初始化。

为了说明一点我不得不撒谎,但现在要揭露全部真相:消息错误暗示a 是真正的问题。你看,因为你有一个对象的实例(Daniel Frey 也提到了这一点)内存(对于成员)必须被初始化(在运行时)。如果成员是 (const) static,如最后一个示例所示,那么它不是 (ny) 类的子对象的一部分,您可以在编译时进行初始化。

【讨论】:

  • 很好的解释。实验方法真的很迷人和辅导。但是,在 cmets @Graznarak 提到的一个问题(“如果您有一个 struct foo 的全局实例,那么它很可能会在静态初始化期间“在编译时”被初始化”)是这样的可能吗?
  • 请注意,实验只允许我们在测试的确切情况下探测一个特定实现的行为。合同约定的程序比偶然的程序更好。
  • @40two 无论对象是全局对象还是const,它都不是编译时值。即使它的初始值是在编译时计算的,这也是一种优化。但是 Nikos 应该提到的是,您可以使用 constexpr 创建一个编译时对象实例——如果 a 被声明为这样,他的示例就可以工作。说使用const static 成员就是这里的答案有点误导。
  • 显而易见的后续问题是,这些成员变量是在构造函数之前还是之后初始化的?我认为这就是 OP 的要求,但我认为没有人回答。
  • @Owl 在构造函数之前没有实例。在构造函数之后,对象就形成了。变量在构造函数中被初始化中(这就是构造函数所做的),特别是“在”成员初始化列表中,如果构造函数没有指定替代成员初始化。通过实验(如本答案中的那个)coliru.stacked-crooked.com/a/5bd1c1d125fcf993 或(许多人似乎更喜欢)检查open-std.org/JTC1/SC22/WG21/docs/papers/2017/n4659.pdf 15.6.2(初始化基数和成员)第 10 条。
【解决方案2】:

成员变量的类内初始化器是用于将它们写入构造器初始化器列表的语法糖,除非已经存在显式初始化器,在这种情况下它们会被忽略。
静态 const 成员的类内初始化器用于常量文字,仍然需要定义(尽管没有初始化器)。

C++ 具有来自 C 的“好像”规则,因此任何导致规定的观察行为的行为都是允许的。
具体来说,这意味着静态对象可以在编译时初始化。

【讨论】:

    【解决方案3】:

    这只是语法糖。还要考虑 instance 通常意味着必须使用正确值初始化的内存。仅仅因为这些值是用不同的语法提供的,并不会改变内存需要初始化的事实——这发生在运行时。

    【讨论】:

    • 除非它不会在运行时发生。
    • @Daniel Frey 首先感谢您的回答,能否请您详细说明静态成员是否也有效,或者这些成员在编译时初始化?跨度>
    • 根据 Bjarne Stroustup 的常见问题解答 (stroustrup.com/C++11FAQ.html#member-init),初始化可以是非常量的,因此在一般情况下不可能在编译时执行它。 (考虑打开数据库连接。)但@Deduplicator 的评论认为由于“as-if”规则,例如静态 POD 的归零当然可以在编译时完成。
    【解决方案4】:

    它本质上是用户提供的初始化值的构造函数的语法糖。您正在为数据成员提供默认值。当您询问这是在编译时还是运行时发生时,答案取决于其使用的上下文。

    希望这些示例会有所帮助。在http://gcc.godbolt.org 中试一试,看看反汇编和编译错误。

    struct S { int size = 3; };
    
    //s's data members are compile time constants
    constexpr S s = {};
    
    //r's data members are run time constants
    const S r = {};
    
    //rr's data members are run time constants, 
    //but we don't know the values in this translation unit
    extern const S rr;
    
    template <int X> class Foo {};
    
    //Ok, s.size is a compile time expression
    Foo<s.size> f; 
    
    //Error, r.size is not a compile time expression
    Foo<r.size> g; 
    
    //Compile time expression, this is same as return 3;
    int foo() { return s.size; }
    
    //This also works
    constexpr int cfoo() { return s.size; }
    
    //Compiler will optimize this to return 3; because r.size is const.
    int bar() { return r.size; }
    
    //Compiler cannot optimize, because we don't know the value of rr.size
    //This will have to read the value of rr.size from memory.
    int baz() { return rr.size; }
    

    正如其他人所展示的,用于 int 和 float 等原始类型的静态数据成员(和全局变量,本质上是相同的)有一些奇怪的规则,它们可以是 const,但仍然可以在编译时上下文中使用,就好像它们是 constexpr .这是为了向后兼容 C 以及过去缺少 constexpr 功能。现在很不幸,因为它只会让理解 constexpr 以及区分运行时表达式和编译时表达式的内容更加混乱。

    【讨论】:

      【解决方案5】:

      拥有int size = 3; 完全等同于拥有int size;,然后在其初始化列表(包括编译器生成的构造函数)中还没有size 的每个构造函数都拥有size(3)

      严格来说,C++ 在“编译时”和“运行时”之间没有区别。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-29
        • 1970-01-01
        • 2021-02-26
        • 2012-06-26
        • 2013-09-17
        • 1970-01-01
        • 2012-09-24
        • 2012-05-30
        相关资源
        最近更新 更多