【问题标题】:C++ Structure InitializationC++ 结构初始化
【发布时间】:2012-07-16 00:42:39
【问题描述】:

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

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

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

如果有其他方法可以实现相同的可读性,请与我分享。

在发布此问题之前,我已经参考了以下链接

  1. C/C++ for AIX
  2. C Structure Initialization with Variable
  3. Static structure initialization with tags in C++
  4. C++11 Proper Structure Initialization

【问题讨论】:

  • 个人观点:在 C++ 中你不需要这种类型的对象初始化,因为你应该使用构造函数。
  • 是的,我想到了,但我有一个大结构数组。使用这种方式对我来说既简单又易读。您是否有任何使用构造函数进行初始化的风格/良好做法,这也提供了更好的可读性。
  • 与编程无关:此地址仅在美国可用。在法国,我们没有“省”,在世界其他地方,没有邮政编码,一个朋友的祖母住在一个小村庄,她的地址是“X女士,邮政编码”小村名”(是的,没有街道)。因此,请仔细考虑将其应用于市场的有效地址是什么;)
  • @MatthieuM。美国没有省份(这可能是加拿大的格式?),但有些州、地区,甚至是小村庄都懒得命名街道。所以地址一致性问题在这里也适用。
  • 尚未有意将其排除在 c++11 之外。但是这个特性将在 c++20 中可用。 open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0329r0.pdf

标签: c++


【解决方案1】:

你有

  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 开始可用的结构的每个成员的标签(请参阅documentation)。

  4. struct 视为 C++ 类 - 在 C++ 结构中实际上是特殊类型的类,其中所有成员都是 public(与标准 C++ 类不同,如果没有明确指定,所有成员都是 private)为以及在使用继承时,它们默认为public: 结构地址 { int street_no; ... char* 邮政编码;

        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", ... }?代码的可读性非常重要。尤其是当您有大型结构(更糟糕的是嵌套结构)时,到处都有未标记的值只会自找麻烦
  • 可扩展性 - 任何依赖于特定顺序的东西都不是一个好主意。缺乏标签也是如此。您想在结构的地址空间中向上或向下移动成员吗?祝你有一个未标记的初始化列表好运(在结构初始化中寻找交换的值是一场噩梦)......你想添加一个新成员吗?再次祝您好运,一切取决于特定订单。

虽然点表示法意味着您键入更多,但使用它获得的好处超过了这个问题,因此我可以推荐它,除非您有一个小型结构,在其结构缺乏变化方面是面向未来的,在在这种情况下,您可以负担得起初始化列表。请记住:无论何时与其他人一起编写易于遵循的代码都是必不可少的。

【讨论】:

    【解决方案2】:

    我知道这个问题已经很老了,但我找到了另一种初始化方式,使用 constexpr 和柯里化:

    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);
    

    此方法也适用于全局静态变量,甚至 constexpr 变量。 唯一的缺点是可维护性差:每次必须使用此方法初始化另一个成员时,都必须更改所有成员初始化方法。

    【讨论】:

    • 这是builder pattern。成员方法可以返回对要修改的属性的引用,而不是每次都创建一个新的结构体
    • @phuclv 实际上,如果@Fabian 这样做,他们将无法像在使用示例中那样执行多个调用。但是,如果他们不使用constexpr,他们只能更改值和return *this; 作为参考。这将导致相同的使用模式,并避免每次都重建一个新对象。
    【解决方案3】:

    您可以通过构造函数进行初始化:

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

    【讨论】:

    • 只有当你控制struct address的定义时才会出现这种情况。此外,POD 类型通常故意没有构造函数和析构函数。
    【解决方案4】:

    我发现这种方法可以用于全局变量,不需要修改原始结构定义:

    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' 的地方使用,所以没关系。)

    【讨论】:

      【解决方案5】:

      在 GNUC++ 中(似乎从 2.5 开始就已经过时了,很久以前 :) 在此处查看答案:C struct initialization using labels. It works, but how?),可以像这样初始化结构:

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

      【讨论】:

        【解决方案6】:

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

        你可以做兰巴闭包:

        // 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-",
          );
        }
        

        这有一些缺点,主要与未初始化的成员有关。从链接的答案 cmets 说,它可以有效地编译,尽管我没有测试过。

        总的来说,我只是认为这是一种巧妙的方法。

        【讨论】:

          【解决方案7】:

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

          #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);
          }
          

          【讨论】:

          • 我用 MinGW C++ 编译器和 Arduino AVR C++ 编译器编译了上述程序,并且都按预期运行。注意#include
          • @run_the_race,这是关于 c++ 标准所说的,而不是给定编译器的行为可能是什么。但是,此功能将在 c++20 中提供。
          • 这仅在结构为 POD 时有效。因此,如果您向其添加构造函数,它将停止编译。
          【解决方案8】:

          正如其他人所说,这是指定的初始化程序。

          此功能是C++20 的一部分

          【讨论】:

          【解决方案9】:

          此功能称为指定初始化程序。它是对 C99 标准的补充。然而,这个特性被排除在 C++11 之外。根据 The C++ Programming Language, 4th edition, Section 44.3.3.2 (C Features Not Adopted by C++):

          C99 的一些新增内容(与 C89 相比)在 C++ 中故意不采用:

          [1] 可变长度数组(VLA);使用向量或某种形式的动态数组

          [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 }
              { }
          

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

          【讨论】:

            【解决方案10】:

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

            这是一个很好的做法,但有时预初始化很方便,就像在您的示例中一样。 OOP 通过抽象类或creational design patterns 解决了这个问题。

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

            作为替代解决方案,我建议使用 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。宏参数也是逗号分隔的,所以需要把初始化器放在括号里,像这样调用

            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)
            

            【讨论】:

              【解决方案11】:

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

              【讨论】:

                【解决方案12】:

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

                我最终在结构中使用了一个构造函数,我相信在您的问题的一些答案中也提出了这一建议。

                让构造函数的参数与公共成员变量同名可能是不好的做法,需要使用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);
                }
                

                【讨论】:

                  【解决方案13】:

                  您甚至可以将 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);
                  

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

                  【讨论】:

                  • 这仍然很危险,因为它允许您向address 添加一个成员,并且代码仍将编译一百万个位置,仅初始化最初的五个成员。结构初始化最好的部分是你可以拥有所有成员const,它会强制你将它们全部初始化
                  【解决方案14】:

                  my question 没有得到令人满意的结果之后(因为 C++ 没有为结构实现基于标签的初始化),我采用了我在这里找到的技巧:Are members of a C++ struct initialized to 0 by default?

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

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

                  这肯定是最接近您最初想要的(将除您要初始化的字段之外的所有字段置零)。

                  【讨论】:

                  • 这不适用于静态初始化的对象
                  • static address temp_address = {}; 将工作。之后填充它取决于运行时,是的。您可以通过提供一个为您执行初始化的静态函数来绕过它:static address temp_address = init_my_temp_address();
                  • 在 C++11 中,init_my_temp_address 可以是 lambda 函数:static address temp_address = [] () { /* initialization code */ }();
                  • 坏主意,它违反了 RAII 原则。
                  • 非常糟糕的主意:将一个成员添加到您的address,您将永远不会知道创建address 的所有地方,现在不要初始化您的新成员。
                  【解决方案15】:

                  如果你想清楚每个初始化器的值是什么,只需将其拆分为多行,并在每一行上添加注释:

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

                  【讨论】:

                  • 我个人喜欢并推荐这种风格
                  • 这样做和实际使用点表示法更准确地访问字段本身有什么区别,如果你担心的是,它不像你在节省任何空间。当涉及到一致和编写可维护的代码时,我真的没有让 C++ 程序员,他们似乎总是想做一些不同的事情来使他们的代码脱颖而出,代码是为了反映正在解决的问题,它不应该是一个成语本身,目标是可靠性和易于维护。
                  • @user1043000 好吧,首先,在这种情况下,您放置成员的顺序是最重要的。如果您在结构的中间添加一个字段,您将不得不返回此代码并寻找插入新初始化的确切位置,这既困难又无聊。使用点符号,您可以简单地将新的初始化放在列表的末尾,而不用担心顺序。如果您碰巧在结构的上方或下方添加与其他成员之一相同的类型(如char*),点符号会更安全,因为没有交换它们的风险。
                  • orip 的评论。如果数据结构定义发生变化,没有人想去寻找初始化,或者找不到它们,或者在编辑它们时出错,事情就会崩溃。
                  • 大多数(如果不是全部)POSIX 结构没有定义的顺序,只有定义的成员。 (struct timeval){ .seconds = 0, .microseconds = 100 } 总是一百微秒,但timeval { 0, 100 } 可能是一百 SECONDS。你不想在艰难的道路上找到类似的东西。
                  【解决方案16】:

                  它没有在 C++ 中实现。 (还有,char* 字符串?我希望不是)。

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

                  【讨论】:

                  • "(还有,char* 字符串?我希望不是)。" - 嗯,这是一个 C 示例。
                  • 我们不能在 C++ 中使用 char* 吗?目前我正在使用它并且它正在工作(可能是我做错了什么)。我的假设是编译器将创建“Hamilton”和“Ontario”的常量字符串并将它们的地址分配给结构成员。改用 const char* 是否正确?
                  • 您可以使用char*,但const char* 更安全,每个人都只使用std::string,因为它更可靠。
                  • 好的。当我阅读“如下所述”时,我认为这是从某个地方复制的示例。
                  【解决方案17】:

                  字段标识符确实是 C 初始化语法。在 C++ 中,只需以正确的顺序给出值,而无需字段名称。不幸的是,这意味着您需要全部提供它们(实际上您可以省略尾随的零值字段,结果将是相同的):

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

                  【讨论】:

                  • 是的,您始终可以使用对齐的结构初始化。
                  • 是的,目前我只使用这种方法(对齐结构初始化)。但是我觉得可读性不好。由于我的结构很大,初始化程序有很多数据,我很难跟踪哪个值分配给了哪个成员。
                  • @DineshP.R.然后写一个构造函数!
                  • @MrLister(或任何人)也许我现在陷入了愚蠢的困境,但是要解释一下构造函数会更好吗?在我看来,向初始化列表提供一堆依赖于顺序的未命名值或向构造函数提供一堆依赖于顺序的未命名值之间几乎没有区别......?
                  • @yano 老实说,我真的不记得为什么我认为构造函数可以解决问题。如果我记得,我会回来找你的。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-01-06
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-05-09
                  相关资源
                  最近更新 更多