c++结构初始化

是否可以在c++中初始化结构,如下所示:

struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};


address temp_address = { .city = "Hamilton", .prov = "Ontario" };

链接在这里在这里提到只能在C中使用这种样式。如果是这样,为什么在c++中不能使用这种样式呢?是否有任何潜在的技术原因,为什么它不是在c++中实现的,或者使用这种风格是不好的做法。我喜欢使用这种初始化方式,因为我的结构体很大,而且这种样式可以让我清楚地了解分配给哪个成员的值。

请与我分享是否有其他方法可以达到同样的可读性。

在提出这个问题之前,我已参考以下连结:

  1. C/ c++ for AIX
  2. C结构初始化变量
  3. c++中带有标签的静态结构初始化
  4. c++ 11正确的结构初始化
686191 次浏览

字段标识符实际上是C初始化式语法。在c++中,只需要按正确的顺序给出值,而不需要字段名。不幸的是,这意味着你需要给他们所有(实际上你可以省略后面的零值字段,结果将是相同的):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 };

它不是在c++中实现的。(同样,char* strings?我希望不会)。

通常情况下,如果你有这么多参数,那就是相当严重的代码味道。但是,为什么不简单地对结构进行值初始化,然后为每个成员赋值呢?

如果你想清楚每个初始化值是什么,只需要把它分成多行,并在每一行上加一个注释:

address temp_addres = {
0,  // street_no
nullptr,  // street_name
"Hamilton",  // city
"Ontario",  // prov
nullptr,  // postal_code
};

我的问题没有得到令人满意的结果之后(因为c++没有为结构实现基于标记的init),我使用了我在这里找到的技巧:c++结构体的成员默认初始化为0吗?

对你来说,它相当于这样做:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

这当然是最接近您最初想要的(除那些您想初始化的字段外,所有字段都为零)。

你甚至可以把Gui13的解决方案打包成一个初始化语句:

struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};




address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

免责声明:我不推荐这种风格

你可以通过构造函数初始化:

struct address {
address() : city("Hamilton"), prov("Ontario") {}
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};

我知道这个问题很老了,但我找到了另一种初始化的方法,使用constexpr和currying:

struct mp_struct_t {
public:
constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
constexpr mp_struct_t another_member(int member) { return {member1, member, member3}; }
constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }


int member1, member2, member3;
};


static mp_struct_t a_struct = mp_struct_t{1}
.another_member(2)
.yet_another_one(3);
此方法也适用于全局静态变量,甚至是常量变量。 唯一的缺点是糟糕的可维护性:每次必须使用此方法使另一个成员可初始化时,所有成员初始化方法都必须更改

我今天遇到了一个类似的问题,我有一个结构体,我想用测试数据填充,这些数据将作为参数传递给我正在测试的函数。我想有这些结构的一个向量,并正在寻找一个单行方法来初始化每个结构。

我最终在struct中使用了一个构造函数,我相信在对您的问题的一些回答中也建议了这一点。

让构造函数的实参与公共成员变量具有相同的名称可能是不好的做法,这需要使用this指针。如果有更好的方法,有人可以建议编辑。

typedef struct testdatum_s {
public:
std::string argument1;
std::string argument2;
std::string argument3;
std::string argument4;
int count;


testdatum_s (
std::string argument1,
std::string argument2,
std::string argument3,
std::string argument4,
int count)
{
this->rotation = argument1;
this->tstamp = argument2;
this->auth = argument3;
this->answer = argument4;
this->count = count;
}


} testdatum;

我在我的测试函数中使用它来调用被测试的函数,使用各种参数,像这样:

std::vector<testdatum> testdata;


testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));


for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}

这是可能的,但前提是初始化的结构体是POD(普通旧数据)结构体。它不能包含任何方法、构造函数,甚至默认值。

在c++中,C风格的初始化式被构造函数所取代,构造函数在编译时可以确保只执行有效的初始化(即初始化后对象成员是一致的)。

这是一个很好的实践,但有时预初始化也很方便,就像在您的示例中一样。OOP通过抽象类或创建设计模式来解决这个问题。

在我看来,使用这种安全的方式消除了简单性,有时安全性的权衡可能过于昂贵,因为简单的代码不需要复杂的设计来保持可维护性。

作为另一种解决方案,我建议使用lambdas来定义宏,以简化初始化,使其看起来几乎像c风格:

struct address {
int street_no;
const char *street_name;
const char *city;
const char *prov;
const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

ADDRESS宏展开为

[] { address _={}; /* definition... */ ; return _; }()

它创建并调用lambda。宏参数也是用逗号分隔的,因此需要将初始化式放在括号中并调用like

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

你也可以写广义宏初始化式

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

但这样的呼唤就不那么美妙了

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

但是你可以很容易地使用INIT宏定义ADDRESS宏

#define ADDRESS(x) INIT(address,x)

我发现这种方式做全局变量,不需要修改原来的结构定义:

struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};

然后声明一个继承自原始结构类型的新类型变量,并使用构造函数初始化字段:

struct temp_address : address { temp_address() {
city = "Hamilton";
prov = "Ontario";
} } temp_address;

虽然没有C风格那么优雅…

对于局部变量,它需要在构造函数的开头添加一个额外的memset(this, 0, sizeof(*this)),所以它显然并不差,@gui13的答案更合适。

(注意,'temp_address'是一个'temp_address'类型的变量,但是这个新类型继承自'address',可以在任何需要'address'的地方使用,所以它是OK的。)

这个特性被称为指定的初始化。它是C99标准的补充。然而,这个特性在c++ 11中被忽略了。根据c++编程语言,第4版,第44.3.3.2节(c++未采用的C特性):

c++故意没有采用C99的一些附加功能(与C89相比):

[1]变长数组(VLAs);使用矢量或某种形式的动态数组

[2]指定初始化式;使用构造函数

C99语法有指定的初始化[参见ISO/IEC 9899:2011, N1570委员会草案- 2011年4月12日]

6.7.9初始化

initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designation_opt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier

另一方面,c++ 11没有指定的初始化[参见ISO/IEC 14882:2011, N3690委员会草案- 2013年5月15日]

8.5初始化

initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
initializer-list:
initializer-clause ...opt
initializer-list , initializer-clause ...opt
braced-init-list:
{ initializer-list ,opt }
{ }

为了达到同样的效果,可以使用构造函数或初始化列表:

正如其他人提到的,这是指定初始化项。

这个特性是C + + 20的一部分

我可能在这里遗漏了一些东西,为什么不呢:

#include <cstdio>
struct Group {
int x;
int y;
const char* s;
};


int main()
{
Group group {
.x = 1,
.y = 2,
.s = "Hello it works"
};
printf("%d, %d, %s", group.x, group.y, group.s);
}

受到这个非常简洁的答案的启发:(https://stackoverflow.com/a/49572324/4808079)

你可以使用lamba闭包:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
int min = 1;
int mean = 3 ;
int mode = 5;
int max = 10;
string name;
string nickname;
... // the list goes on
}

class SomeClass {
static const inline SomeBigStruct voiceAmps = []{
ModulationTarget $ {};
$.min = 0;
$.nickname = "Bobby";
$.bloodtype = "O-";
return $;
}();
}

或者,如果你想要非常花哨的话

#define DesignatedInit(T, ...)\
[]{ T ${}; __VA_ARGS__; return $; }()


class SomeClass {
static const inline SomeBigStruct voiceAmps = DesignatedInit(
ModulationTarget,
$.min = 0,
$.nickname = "Bobby",
$.bloodtype = "O-",
);
}

这样做有一些缺点,主要与未初始化的成员有关。从链接的答案评论说,它编译有效,虽然我没有测试它。

总的来说,我认为这是一个很好的方法。

在gnuc++中(似乎从2.5开始就过时了,很久以前:)看到答案:使用标签初始化C结构。这是可行的,但如何实现呢?),可以像这样初始化一个结构体:

struct inventory_item {
int bananas;
int apples;
int pineapples;
};


inventory_item first_item = {
bananas: 2,
apples: 49,
pineapples: 4
};

你有

  1. 标准初始化列表

    address temp_address {
    /* street_no */,
    /* street_name */,
    ...
    /* postal_code */
    };
    
    
    address temp_address2 = {
    /* street_no */,
    /* street_name */,
    ...
    /* postal_code */
    }
    
  2. 点符号

    address temp_address;
    temp_address.street_no = ...;
    temp_address.street_name = ...;
    ...
    temp_address.postal_code = ...;
    
  3. 指定的聚合初始化,其中初始化列表包含从c++ 20以后可用的结构的每个成员的标签(参见文档)。

  4. 像c++类一样对待struct -在c++结构中,实际上是特殊类型的类,其中所有成员都是public(不像标准c++类,如果没有明确指定,所有成员都是private),以及在使用继承时,它们默认为public:

    struct Address {
    int street_no;
    ...
    char* postal_code;
    
    
    Address (int _street_no, ... , char* _postal_code)
    : street_no(_street_no),
    ...
    postal_code(_postal_code)
    {}
    }
    
    
    ...
    
    
    Address temp_address ( /* street_no */, ..., /* postal_code */);
    

当涉及到初始化结构的方式时,你应该考虑以下方面:

  • 可移植性 -不同的编译器,不同的c++标准完整性和不同的c++标准会限制你的选择。如果你必须使用c++ 11编译器,但又想使用c++ 20指定的聚合初始化,那你就不走运了
  • 可读性 -哪个更可读:temp_address.city = "Toronto"还是temp_address { ..., "Toronto", ... }?代码的可读性非常重要。特别是当你有一个大的结构(嵌套的更糟),到处都是未标记的值只是自找麻烦
  • 可伸缩性 -任何依赖于特定顺序的东西都不是一个好主意。缺乏标签也是如此。要在结构的地址空间中向上或向下移动成员?祝你在未标记的初始化列表中好运(在结构初始化中寻找交换的值是一场噩梦)……您想添加新成员吗?再次祝你在任何取决于特定顺序的事情上好运。

虽然点表示法意味着你输入更多,但你从使用它中得到的好处超过了这个问题,因此我建议你使用它,除非你有一个小的结构,它的结构缺乏变化,在这种情况下,你可以使用一个初始化列表。记住:无论何时与他人合作,编写易于遵循的代码都是至关重要的。