类内成员初始化在编译时或运行时进行

Does in class member initialization takes place at compile time or run-time?

本文关键字:运行时 编译 成员 初始化      更新时间:2023-10-16

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

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

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

首先是的,如前所述,它是语法糖。但是,由于规则可能太多而记不住,这里有一个逻辑实验可以帮助您了解编译时发生了什么,以及什么不是

您的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;

或者(更清楚地)视觉工作室2013年告诉我们

错误C2975:"N":"example"的模板参数无效,应为编译时常量表达式

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

编译时需要什么

有一种古老的语法可以实现

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

现在它编译了,但要注意的是(从技术和逻辑上讲)在类初始化中不再

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

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

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

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

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

希望这些例子会有所帮助。试一下http://gcc.godbolt.org并查看disassembly和编译错误。

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以及运行时表达式和编译时表达式的区别变得更加混乱。

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

严格地说,C++在"编译时"answers"运行时"之间没有区别。