函数已有主体

随着现代C ++和标准的每次修订,我们可以用更舒适的方式初始化类的字段:静态和非静态:有非静态数据成员初始化(从C ++ 11开始)和内联变量(对于静态)成员(从C ++ 17开始)。

在这篇博文中,你将学习如何使用语法,以及20多年来从C ++ 11到C ++ 14,从C ++ 17到C ++的语法变化。

数据成员的初始化

在C ++ 11之前,如果你有一个类成员,你只能通过构造函数中的初始化列表将其初始化为默认值。

//pre c++ 11 class:struct SimpleType { int field;std::字符串名称;SimpleType():字段(0),名称(& # 34;你好世界& # 34;){}}从C ++ 11开始,语法有所改进。您可以初始化该字段,并用名称替换声明:

//自c++ 11:struct SimpleType { int field = 0;//现在起作用!std::字符串名称{ & # 34;你好世界& # 34;}//alternate way with { } simpletype(){ }如您所见,变量将在声明位置获取其默认值。不需要在构造函数中设置值。

这个函数叫做*非静态数据成员初始化*或简称NSDMI。

更重要的是,从C ++ 17开始,我们可以使用内联变量来初始化静态数据成员:

struct other type { static const int value = 10;静态内联STD::string class name = & # 34;同学们好& # 34;;OtherType() {}}现在,不需要在对应的cpp文件中定义className。编译器保证所有编译单元只能看到静态成员的一个定义。以前,在C ++ 17之前,您必须将定义放入一个cpp文件中。

注意,对于常量整型静态字段(值),即使在C ++ 98中,我们也可以“就地”初始化它们。

让我们探索这些有用的函数:NSDMI和内联变量。我们将看到一些例子,以及这些特性在过去几年中是如何改进的。

NSDMI-非静态数据成员初始化

简而言之,编译器将初始化该字段,就像您将它写入构造函数初始化列表一样。

SimpleType(): field(0) {}让我们仔细看看:

怎么运行的

有了一点“机”,我们就可以看到编译器什么时候进行初始化了。

让我们考虑以下类型:

struct SimpleType { int a { initA()};int b { initB()};// …};initA()和initB()函数的实现有副作用,它们会记录其他消息:

int initA(){ STD::cout & lt;& lt"initA()被调用\ n & # 34;返回1;} STD::string initB(){ STD::cout & lt;& lt"initB()被调用\ n & # 34;return & # 34你好& # 34;;这让我们可以知道何时调用代码。

例如:

struct SimpleType { int a { initA()};STD::string b { initB()};SimpleType(){ } SimpleType(int x):a(x){ } };并使用:

STD::cout & lt;& lt"简单类型t10 \ n & # 34;SimpleType t0STD::cout & lt;& lt"简单类型t1(10)\ n & # 34;;SimpleType t1(10);输出:

simple type 0:inita()called inib()called simple type 1(10):initb()called t0默认情况下是初始化的,所以两个字段都用它们的默认值初始化。

第二种情况,对于t1,默认只初始化一个值,另一个值来自构造函数参数。

您可能已经猜到了,编译器将初始化该字段,就像初始化成员初始化列表中的字段一样。因此,它们在调用构造函数体之前获取默认值。

换句话说,编译器将扩展代码:

int a { initA()};STD::string b { initB()};simpletype(){ } simpletype(int x):a(x){ } Enter

int a;STD::string b;Simpletype (): a (inita()),b (initb ()) {} simpletype(int x):a(x),b(initb()){ }其他构造函数呢?

复制和移动构造函数

编译器将执行所有构造函数中的字段初始化,包括复制和移动构造函数。但是,当copy或move构造函数是默认值时,不需要进行额外的初始化。

参见示例:

struct SimpleType { int a { initA()};STD::string b { initB()};SimpleType(){ } SimpleType(const SimpleType & other){ STD::cout & lt;& lt"复制ctor \ n & # 34;a = other.ab = other.b};};和使用案例:

简单类型t1;STD::cout & lt;& lt"简单类型T2 = t1:\ n & # 34;;SimpleType t2 = t1输出:

simple type t1:inita()called initb()called simple type 2 = t1:inita()called initb()called copy。点击此处查看代码@Wandbox。

在上面的例子中,编译器用默认值初始化这些字段。这就是为什么最好在复制构造函数中使用初始化列表的原因:

SimpleType(const SimpleType & other):a(other . a),b(other . b){ STD::cout & lt;& lt"复制ctor \ n & # 34;};我们有:

simple type 1:inita()called initb()called simple type 2 = t1:copy ctor如果依赖编译器生成的默认复制构造函数,也会发生同样的情况:

SimpleType(const SimpleType & other)= default;当您移动构造函数时,也会发生同样的事情。

NSDMI的优势容易写您确定每个成员都正确初始化。声明和默认值在同一位置

当我们有几个构造函数时特别有用。以前,我们将不得不为成员复制初始化代码,或者编写一个自定义方法InitMembers(),该方法将在构造函数中调用。现在,您可以执行默认初始化,构造函数将仅执行其特定工作…

NSDMI是否有负面影响?

很难提出缺点,但让我们试试:

性能:当您具有性能关键型数据结构(例如Vector3D类)时,可能需要使用“空”初始化代码。您可能会有未初始化的数据成员的风险,但是您将保存一些说明。使类在C ++ 11中不聚合,但在C ++ 14中不聚合。请参阅有关C ++ 14更改的部分。由于默认值位于头文件中,因此任何更改都可能导致需要重新编译依赖的编译单元。如果仅在实现文件中设置值,则不是这种情况。感谢Yehezkel在评论中提到它!这个缺点也适用于静态变量,我们将在后面讨论。

你还有其他问题吗?

聚合,NSDMI的C ++ 14更新

最初,在C ++ 11中,如果用默认成员初始化,则类不能是聚合类型:

结构点{ float x = 0.0ffloat y = 0.0f};//韩元& # 39;t用C++11Point myPt { 10.0f,11.0f}编译;我不知道这个问题,但是Shafik Yaghmour在文章下面的评论里指出来了。

在C ++ 11规范中,这样的聚合类型初始化是不允许的,但是在C ++ 14中,这个要求已经被删除了。链接到StackOverflow问题的详细信息。

好在在C ++ 14中已经修复了,所以

点myPt { 10.0f,11.0 f };编译如预期,请看@Wandbox。

位域的C ++ 20更新

从C ++ 11开始,代码只考虑“通用”字段…但是类中的位字段呢?

类类型{ unsigned int value:4;};这只是C ++ 20中的最新变化,它使您能够编写:

类类型{ unsigned int value:4 = 0;无符号整数秒:4 { 10 };};C ++ 20接受的建议是C ++ 20 P0683的默认位域初始化程序。

内联变量C ++ 17

到目前为止,我们已经讨论了非静态数据成员。我们在类中声明和初始化静态变量方面有什么改进吗?

在C ++ 11/14中,必须在相应的cpp文件中定义一个变量:

//头文件:struct other type { static int class counter;// …};//实现,CPP fileint other type::class counter = 0;幸运的是,对于C ++ 17,我们也有内联变量,这意味着您可以在一个类中静态内联变量,而无需在cpp文件中定义它们。

//一个头文件,c++ 17:struct other type { static inline int class counter = 0;// …};编译器保证为所有包含类声明的翻译单元精确定义静态变量。内联变量还是静态类变量,所以会在main()调用函数之前进行初始化(你可以在我的独立文章中读到更多,程序启动时静态变量会怎样?)。

这个特性使得开发只有头文件的库更加容易,因为不需要为静态变量创建cpp文件或者使用一些技巧将它们保存在头文件中。

和案例自动

因为我们可以在类中声明和初始化变量,所以有一个有趣的问题auto。我们能用它吗?这似乎是一种自然的方法,将遵循AAA(几乎总是自动的)规则。

您可以使用自动静态变量:

class Type { static inline auto theMeaningOfLife = 42;// int推演};但不是作为类的非静态成员:

类类型{ auto my field { 0 };//错误自动参数{ 10.5 f };//错误};可惜auto不支持。例如,在海湾合作委员会,我得到了

错误:用占位符& # 39;声明了非静态数据成员;auto & # 39虽然静态成员只是静态变量,这也是为什么编译器推断类型相对容易,但对于常规成员就没那么容易了。主要因为类型和类的布局可能是循环依赖的。如果你对全文感兴趣,可以看看cor3ntin博客上的以下精彩讲解:自动非静态数据成员初始化器的| cor3ntin。

CTAD案例-类模板参数推导

同样,对于非静态成员变量和CTAD,我们也有一些限制:

它适用于静态变量:

class Type { static inline STD::vector int { 1,2,3,4,5,6,7 };//推导出的向量& ltint & gt};但不是作为非静态成员:

class Type { STD::vector int { 1,2,3,4,5,6,7 };//错误!};在GCC 10.0上,我得到了

error: 'vector' does not name a type编译器支持

函数已有主体错误:& # 39;矢量& # 39;不命名类型编译器支持

摘要

在本文中,我们回顾了现代C ++如何改变类中成员的初始化。

在C ++ 11中,我们获得了NSDMI-非静态数据成员初始化。现在,您可以声明一个成员变量并用默认值初始化它。在调用构造函数初始化列表中的每个构造函数体之前,将执行初始化。

NSDMI在C ++ 14(聚合)和C ++ 20(现在支持位字段)中得到了改进。

更重要的是,在C ++ 17中,我们获得内联变量,这意味着您可以声明和初始化静态成员,而不必在相应的cpp文件中这样做。

这是一个结合了这些功能的“汇总”示例:

结构窗口{ inline static unsigned int default _ width = 1028;内联静态无符号int default _ height = 768无符号int _ width { default _ width };无符号int _ height { default _ height };无符号int _ flags:4 { 0 };STD::string _ title { & # 34;默认窗口& # 34;};Window(){ } Window(STD::string title):_ title(STD::move(title)){ }//…};

为简单起见,default_width和default_height是静态变量。例如,您可以从配置文件中加载这些静态变量,然后使用它们来初始化默认的窗口状态。

轮到你了

你在你的项目中使用NSDMI吗?你使用静态内联变量作为类成员吗?

你在代码中使用它了吗?

原地址:https://www . bfilipek . com/2015/02/non-static-data-members-initial ization . html。

代码优化的越多,实践和思考的越多,越能发现C/C++的优势。

关注我:带你遨游代码的世界~私信 “资料” 获取更多~

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论