C ++構造の初期化


278

以下に示すように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の適切な構造の初期化

20
個人的な世界観:代わりにコンストラクターを使用する必要があるため、C ++でこのスタイルのオブジェクト初期化は必要ありません。
フィリップケンドール

7
はい私はそれを考えました、しかし私には大きな構造の配列があります。この方法を使用するのは簡単で読みやすいでしょう。読みやすさを向上させるコンストラクタを使用して初期化するスタイル/良い習慣はありますか?
Dinesh PR

2
コンストラクターと組み合わせて、ブーストパラメーターライブラリを検討しましたか?boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
トニー・デルロイ

18
プログラミング関連ではありません。このアドレスは米国でのみ正常に機能します。フランスには「都道府県」がありません。世界の他の地域には郵便番号がありません。友人の祖母が住所が「Ms X、postal-code」であるような小さな村に住んでいます小さな村の名前」(はい、通りはありません)。したがって、これを適用する市場で有効な住所が何であるかを注意深く検討してください;)
Matthieu M.

5
@MatthieuM。米国には州はありませんが(これはカナダの形式でしょうか?)、通りに名前を付けるのに邪魔にならない州、準州、さらには小さな村ですらあります。したがって、アドレス適合の問題はここでも当てはまります。
Tim

回答:


165

各初期化子の値を明確にしたい場合は、それぞれにコメントを付けて、複数の行に分割します。

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

7
私は個人的にこのスタイルが好きで、お勧めします
Dinesh PR

29
それを行うことと実際にドット表記を使用してフィールド自体に正確にアクセスすることの違いは何ですか?それが問題である場合、スペースを節約しているようではありません。一貫性があり、保守可能なコードを作成することになると、C ++プログラマーは実際には得られません。彼らは常にコードを目立たせるために何か違うことをしたいようです。コードは、解決すべき問題を反映することを目的としています。独自のイディオムであり、信頼性とメンテナンスの容易さを目指しています。

5
@ user1043000よく、たとえば、この場合は、メンバーを配置する順序が最も重要です。構造の中央にフィールドを追加する場合、このコードに戻って、新しい初期化を挿入する正確な場所を探す必要があります。これは困難で退屈です。ドット表記を使用すると、順序を気にすることなく、新しい初期化をリストの最後に置くことができます。またchar*、構造体の上または下にある他のメンバーの1つと同じタイプ(など)を追加した場合、それらを交換するリスクがないため、ドット表記はより安全です。
Gui13

6
oripのコメント。データ構造の定義が変更され、誰も初期化を探すつもりがない、またはそれらをすべて見つけることができない、またはそれらを誤って編集した場合、状況はばらばらになります。
エドワードフォーク

3
ほとんど(すべてではない)のPOSIX構造体には、定義された順序はなく、定義されたメンバーだけがあります。(struct timeval){ .seconds = 0, .microseconds = 100 }常に百マイクロ秒になりますが、timeval { 0, 100 }百かもしれませんSECONDS。あなたは難しい方法でそのようなものを見つけたくありません。
yyny 2018

98

私の質問の結果、満足のいく結果が得られなかった後(C ++は構造にタグベースの初期化を実装していないため)、私はここで見つけたトリックを採用しました:C ++構造体のメンバーはデフォルトで0に初期化されていますか?

あなたにとってそれはそれをすることになるでしょう:

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

これは確かに最初に必要なものに最も近いものです(初期化したいものを除くすべてのフィールドをゼロにします)。


10
これは静的に初期化されたオブジェクトに対しては機能しません
user877329 '10

5
static address temp_address = {};動作します。後で埋めることはランタイム次第です、はい。initを行う静的関数を提供することで、これを回避できますstatic address temp_address = init_my_temp_address();
Gui13 2014

C ++ 11では、init_my_temp_addressラムダ関数とすることができる:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
悪い考えです、それはRAIIの原則に違反しています。
hxpax 2017年

2
本当に悪い考え:メンバーを1人追加するaddressと、を作成するすべての場所を知ることがaddressできなくなり、新しいメンバーを初期化できなくなります。
mystery_doctor


17

フィールド識別子は、確かにC初期化構文です。C ++では、フィールド名なしで正しい順序で値を指定するだけです。残念ながら、これはそれらすべてを与える必要があることを意味します(実際には、末尾のゼロ値フィールドを省略でき、結果は同じになります):

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

1
はい、いつでも整列構造体の初期化を使用できます。
texasbruce 2012

3
はい、現在この方法のみを使用しています(Aligned Struct Initialization)。ただ、読みやすさは良くないと思います。私の構造は大きいので、初期化子には非常に多くのデータがあり、どの値がどのメンバーに割り当てられているかを追跡することは困難です。
Dinesh PR

7
@ DineshP.R。次に、コンストラクターを作成します。
リスター氏2012

2
@MrLister(または誰でも)おそらく私は今、愚かさの雲に閉じ込められていますが、コンストラクターがはるかに優れていることを説明するのに気を付けていますか?イニシャライザリストに順序に依存する名前のない値の束を提供することと、コンストラクタに順序に依存する名前のない値の束を提供することの間に少し違いがあるように思えます...?
矢野

1
@yano正直なところ、コンストラクターが問題の答えになると思った理由を本当に思い出しません。私が覚えているなら、私はあなたに戻ってきます。
リスター氏

13

この機能は、指定イニシャライザと呼ばれます。これは、C99標準への追加です。ただし、この機能はC ++ 11から除外されました。C ++プログラミング言語、第4版、セクション44.3.3.2(C ++で採用されていない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 }
    { }

同じ効果を達成するには、コンストラクターまたは初期化リストを使用します。


9

あなたはただctorを介して初期化することができます:

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

9
これは、の定義を制御する場合にのみ当てはまりますstruct address。また、PODタイプには、多くの場合、意図的にコンストラクターとデストラクターがありません。
user4815162342 14年

7

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し、コードは依然として元の5つのメンバーを初期化するだけの100万桁でコンパイルされるため、依然として危険です。構造体の初期化の最良の部分は、すべてのメンバーconstを持つことができ、すべてのメンバーを強制的に初期化することです
mystery_doctor

7

私はこの質問がかなり古いことを知っていますが、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,     member2}; }
        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変数でも機能します。唯一の欠点は、保守性が悪いことです。このメソッドを使用して別のメンバーを初期化可能にする必要があるたびに、すべてのメンバーの初期化メソッドを変更する必要があります。


1
これはビルダーパターンです。メンバーメソッドは、毎回新しい構造体を作成する代わりに、変更するプロパティへの参照を返すことができます
phuclv

6

ここに何かが足りないかもしれませんが、なぜですか:

#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 <cstdio>に注意してください
run_the_race

4
@run_the_race、これはc ++標準が言うことについてであり、特定のコンパイラの動作が何であるかではありません。ただし、この機能はc ++ 20で提供されます。
ジョン

これは、構造体がPODの場合にのみ機能します。そのため、コンストラクタを追加するとコンパイルが停止します。
oromoiluig

5

C ++では実装されていません。(また、char*文字列ですか?私はそうではないと思います)。

通常、非常に多くのパラメーターがある場合、それはかなり深刻なコードのにおいです。しかし、代わりに、単純に構造体を値で初期化してから、各メンバーを割り当てないのはなぜですか?


6
「(また、char*文字列?私はそうしないといいのですが。」)-まあ、それはCの例です。
Ed S.

C ++でchar *を使用できませんか?現在、私はそれを使用しており、動作しています(何か問題がある可能性があります)。私の想定では、コンパイラは「ハミルトン」と「オンタリオ」の定数文字列を作成し、それらのアドレスを構造体メンバーに割り当てます。代わりにconst char *を使用することは正しいでしょうか?
Dinesh PR

8
使用できますchar*が、const char*タイプセーフであり、std::string信頼性が高いため、誰もが使用します。
パピー

OK。「以下のように」を読んだとき、どこかからコピーした例だと思いました。
Ed S.

2

私はグローバル変数に対してこれを行うこの方法を見つけました、それは元の構造定義を変更する必要はありません:

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」が期待されるすべての場所で使用できるため、問題ありません。)


1

私は今日同様の問題に直面しました。そこでは、テストしている関数に引数として渡されるテストデータで埋める構造体があります。私はこれらの構造体のベクトルが必要で、各構造体を初期化するためのワンライナーメソッドを探していました。

私は構造体のコンストラクタ関数を使用することになりました。これは、質問へのいくつかの回答でも提案されたと思います。

コンストラクターへの引数にパブリックメンバー変数と同じ名前を付けることは、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);
}

1

それは可能ですが、初期化している構造体がPOD(プレーンな古いデータ)構造体である場合のみです。メソッド、コンストラクタ、さらにはデフォルト値を含めることはできません。


1

C ++では、Cスタイルのイニシャライザは、有効な初期化のみが実行されるようにコンパイル時に保証できる(つまり、初期化後にオブジェクトメンバーが一貫している)コンストラクタに置き換えられました。

これは良い方法ですが、例のように、事前初期化が便利な場合もあります。OOPは、抽象クラスまたは創造的なデザインパターンによってこれを解決します

私の意見では、この安全な方法を使用すると、単純さが失われ、場合によっては、セキュリティのトレードオフが高すぎる可能性があります。単純なコードは、保守性を維持するために洗練された設計を必要としないためです。

別の解決策として、ラムダを使用してマクロを定義し、初期化を単純化して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 _; }()

ラムダを作成して呼び出します。マクロパラメータもカンマで区切られているため、イニシャライザを角括弧に入れて、次のように呼び出す必要があります。

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)


0

この本当にきちんとした答えに触発されました:(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-",
  );
}

これにはいくつかの欠点があり、主に初期化されていないメンバーに関係しています。リンクされた回答のコメントのコメントから、私はテストしていませんが、効率的にコンパイルされます。

全体として、それはきちんとしたアプローチだと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.