便利なC ++構造体の初期化


141

「ポッド」C ++構造体を初期化する便利な方法を見つけようとしています。ここで、次の構造体について考えてみましょう。

struct FooBar {
  int foo;
  float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;

これをC(!)で簡単に初期化したい場合は、次のように書くだけです。

/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C

次の表記法は明示的に避けたいことに注意してください。これは、将来構造体で何かを変更すると首を骨折するように作られているように思われるためです。

/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?

C ++で/* A */例と同じ(または少なくとも同様)を実現するには、ばかばかしいコンストラクターを実装する必要があります。

FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);

どちらがお湯を沸かすのに適していますが、怠惰な人々には適していません(怠惰は良いことですよね?)。また、/* B */どの値がどのメンバーに渡されるかを明示的に示していないため、例と同じくらい悪いです。

だから、私の質問は基本的に私が/* A */C ++ と同じかそれ以上のことをどのように達成できるかです?あるいは、なぜこれをしたくないのか(つまり、私の精神的パラダイムが悪いのか)の説明で大丈夫でしょう。

編集

することで便利な、私はまた意味保守性非冗長


2
Bの例は、あなたが取得するのと同じくらい近いと思います。
Marlon、

2
例Bが「悪いスタイル」であることはわかりません。各メンバーをそれぞれの値で順番に初期化しているので、それは私には理にかなっています。
Mike Bailey、

26
マイク、どの値がどのメンバーに当てはまるのかはっきりしないので、それは悪いスタイルです。構造体の定義に移動して確認し、メンバーを数えて各値の意味を見つける必要があります。
jnnnnn 2013年

9
さらに、FooBarの定義が将来変更されると、初期化が壊れる可能性があります。
エドワードフォーク

初期化が長く複雑になる場合は、ビルダーパターンを忘れないでください
そり

回答:


20

指定された初期化はc ++ 2aでサポートされますが、GCC、Clang、およびMSVCによって正式にサポートされているため、待つ必要はありません。

#include <iostream>
#include <filesystem>

struct hello_world {
    const char* hello;
    const char* world;
};

int main () 
{
    hello_world hw = {
        .hello = "hello, ",
        .world = "world!"
    };

    std::cout << hw.hello << hw.world << std::endl;
    return 0;
}

GCC Demo MSVC Demo


警告のエンプター:後で構造体の最後にパラメーターを追加した場合、古い初期化は初期化されずにサイレントコンパイルすることに注意してください。
Catskul

1
@Catskul号は、それが初期化され、ゼロで初期化になり、空の初期化子リストと。
ivaigult

あなたが正しい。ありがとうございました。明確にする必要があります。残りのパラメータは事実上デフォルトで初期化されます。私が指摘したいのは、これがPODタイプの完全な明示的な初期化を強制するのに役立つことを期待している人は失望するということです。
Catskul

43

以来style ACで許可されていません++、あなたはしたくないstyle B、その後どのように使用についてstyle BX

FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 };  // :)

少なくともある程度は助けてください。


8
+1:(コンパイラのPOVからの)正しい初期化を実際に保証するわけではありませんが、確実に読者を助けます...コメントは常に同期している必要があります。
Matthieu M.

18
コメントは、私が間に新しいフィールドを挿入した場合破壊される構造体の初期化を妨げるものではないfooし、bar将来的には。Cは必要なフィールドを初期化しますが、C ++は初期化しません。そして、これが問題のポイントです-C ++で同じ結果を達成する方法。つまり、Pythonはこれを名前付き引数で行います。C-「名前付き」フィールドで行います。C++にも何かがあるはずです。
dmitry_romanov 2013

2
コメントは同期していますか?休憩してください。安全は窓を通り抜けます。パラメータとブームを並べ替えます。ではるかに良いexplicit FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) 明示的なキーワードに注意してください。安全性に関しては、基準を破るのも良い方法です。Clang:-Wno-c99-extensions
Daniel O

@DanielW、それは何がより良いか、何がそうでないかについてではありません。OPはスタイルA(c ++ではない)、B、またはCを必要としていないため、この回答は有効なすべてのケースをカバーしています。
iammilind

@iammilind OPのメンタルパラダイムがなぜ悪いのかについてのヒント、答えを改善できると思います。これは今のところ危険だと思います。
Daerst、2015

10

あなたはラムダを使うことができます:

const FooBar fb = [&] {
    FooBar fb;
    fb.foo = 12;
    fb.bar = 3.4;
    return fb;
}();

このイディオムの詳細については、Herb Sutterのブログをご覧ください。


1
このようなアプローチでは、フィールドが2回初期化されます。コンストラクタに入ると。第二はfb.XXX = YYYです。
Dmytro Ovdiienko

9

それらを記述する関数に定数を抽出します(基本的なリファクタリング):

FooBar fb = { foo(), bar() };

スタイルが使いたくないスタイルに非常に近いことは知っていますが、定数値を簡単に置き換えることができ、変更された場合はそれらを説明することができます(コメントを編集する必要がないため)。

(怠惰なため)できるもう1つのことは、コンストラクターをインラインにすることです。そのため、それほど多く入力する必要はありません( "Foobar ::"を削除し、hファイルとcppファイルの切り替えに費やした時間)。

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};

1
構造体を一連の値ですばやく初期化できる場合は、この質問を読んでいる人に、この回答の下部のコードスニペットでスタイルを選択することを強くお勧めします。
kayleeFrye_onDeck 2018

8

あなたの質問は、関数でさえ次の理由でやや難しいです:

static FooBar MakeFooBar(int foo, float bar);

次のように呼び出すことができます:

FooBar fb = MakeFooBar(3.4, 5);

組み込みの数値型の昇格および変換ルールがあるためです。(Cは本当に強く型付けされたことがありません)

C ++では、テンプレートと静的アサーションを使用して、必要なことを実現できます。

template <typename Integer, typename Real>
FooBar MakeFooBar(Integer foo, Real bar) {
  static_assert(std::is_same<Integer, int>::value, "foo should be of type int");
  static_assert(std::is_same<Real, float>::value, "bar should be of type float");
  return { foo, bar };
}

Cでは、パラメーターに名前を付けることができますが、それ以上は得られません。

一方、名前付きパラメータだけが必要な場合は、面倒なコードをたくさん記述します。

struct FooBarMaker {
  FooBarMaker(int f): _f(f) {}
  FooBar Bar(float b) const { return FooBar(_f, b); }
  int _f;
};

static FooBarMaker Foo(int f) { return FooBarMaker(f); }

// Usage
FooBar fb = Foo(5).Bar(3.4);

また、必要に応じて、タイププロモーション保護を適用できます。


1
「C ++では、希望どおりの結果が得られます」:OPは、パラメーターの順序の混同を防止するための助けを求めていませんでしたか?あなたが提案するテンプレートはどのようにそれを達成しますか?簡単にするために、2つのパラメーターがあり、両方ともintであるとします。
最大

@max:タイプが異なる場合(相互に変換可能であっても)、それがOPの状況である場合にのみ、それが防止されます。タイプを区別できない場合はもちろん機能しませんが、それはまったく別の問題です。
Matthieu M.17年

わかった。ええ、これらは2つの異なる問題であり、2番目の問題は現時点ではC ++で適切な解決策がないと思います(ただし、C ++ 20は集約初期化でC99スタイルのパラメーター名のサポートを追加しているようです)。
最大

6

多くのコンパイラのC ++フロントエンド(GCCおよびclangを含む)は、C初期化子の構文を理解しています。可能であれば、その方法を使用してください。


16
これはC ++標準に準拠していません。
ビットマスク

5
それが非標準であることは知っています。しかし、それを使用できる場合は、それでも構造体を初期化する最も賢明な方法です。
Matthias Urlichs 2013年

2
あなたは間違ったコンストラクタがプライベート作り、xとyの種類を保護することができますprivate: FooBar(float x, int y) {};
dmitry_romanov

4
clang(llvmベースのc ++コンパイラ)もこの構文をサポートしています。残念ながら、それは標準の一部ではありません。
nimrodm 2013年

CイニシャライザーはC ++標準の一部ではないことは誰もが知っています。しかし、多くのコンパイラはそれを理解しており、質問があれば、どのコンパイラがターゲットにされているかはわかりませんでした。したがって、この回答に反対票を投じないでください。
Matthias Urlichs

4

C ++のさらに別の方法は

struct Point
{
private:

 int x;
 int y;

public:
    Point& setX(int xIn) { x = Xin; return *this;}
    Point& setY(int yIn) { y = Yin; return *this;}

}

Point pt;
pt.setX(20).setY(20);

2
関数型プログラミング(つまり、関数呼び出しの引数リストにオブジェクトを作成する)は面倒ですが、それ以外の場合は本当にすてきなアイデアです!
ビットマスク

27
オプティマイザはおそらくそれを減らしますが、私の目は減らしません。
Matthieu M.

6
2つの単語:argh ... argh!これは、 'Point pt;で公開データを使用するよりも優れています。pt.x = pt.y = 20; `?または、カプセル化が必要な場合、これはコンストラクタよりも優れていますか?
OldPeculier

3
パラメーターの順序のコンストラクター宣言を確認する必要があるため、コンストラクターよりも優れています... x、yまたはy、xですが、これを示した方法は呼び出しサイトで明らかです
parapura rajkumar

2
これは、const構造体が必要な場合は機能しません。または、初期化されていない構造体を許可しないようコンパイラーに伝えたい場合。この方法で本当に実行したい場合は、少なくともセッターにinline
Matthias Urlichs、2015年

3

オプションD:

FooBar FooBarMake(int foo, float bar)

リーガルC、リーガルC ++。POD向けに簡単に最適化できます。もちろん、名前付き引数はありませんが、これはすべてのC ++と同じです。名前付き引数が必要な場合は、Objective Cをお勧めします。

オプションE:

FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;

リーガルC、リーガルC ++。名前付き引数。


12
FooBar fb = {};C ++で使用できるmemsetの代わりに、すべての構造体メンバーをデフォルトで初期化します。
OO Tiib

@ÖöTiib:残念ながらそれは違法なCです。
CBベイリー、

3

私はこの質問が古いことを知っていますが、C ++ 20が最終的にこの機能をCからC ++にもたらすまで、これを解決する方法があります。これを解決するには、static_assertsを指定したプリプロセッサマクロを使用して、初期化が有効かどうかを確認します。(私はマクロが一般的に悪いことを知っていますが、ここでは別の方法は見ていません。)以下のサンプルコードを参照してください:

#define INVALID_STRUCT_ERROR "Instantiation of struct failed: Type, order or number of attributes is wrong."

#define CREATE_STRUCT_1(type, identifier, m_1, p_1) \
{ p_1 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_2(type, identifier, m_1, p_1, m_2, p_2) \
{ p_1, p_2 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_4(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3, m_4, p_4) \
{ p_1, p_2, p_3, p_4 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_4) >= (offsetof(type, m_3) + sizeof(identifier.m_3)), INVALID_STRUCT_ERROR);\

// Create more macros for structs with more attributes...

次に、const属性を持つ構造体がある場合、これを行うことができます。

struct MyStruct
{
    const int attr1;
    const float attr2;
    const double attr3;
};

const MyStruct test = CREATE_STRUCT_3(MyStruct, test, attr1, 1, attr2, 2.f, attr3, 3.);

考えられるすべての属性に対してマクロが必要であり、マクロの呼び出しでインスタンスのタイプと名前を繰り返す必要があるため、これは少し不便です。また、アサートは初期化の後に来るため、returnステートメントでマクロを使用することはできません。

しかし、問題は解決します。構造体を変更すると、呼び出しはコンパイル時に失敗します。

C ++ 17を使用している場合は、同じ型を強制することで、これらのマクロをより厳密にすることもできます。例:

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_1) == typeid(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_2) == typeid(identifier.m_2), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_3) == typeid(identifier.m_3), INVALID_STRUCT_ERROR);\

名前付き初期化子を許可するC ++ 20提案はありますか?
MAEL二尊院


2

/* B */C ++では方法は問題ありませんが、C ++ 0xは構文を拡張するため、C ++コンテナにも役立ちます。なぜあなたはそれを悪いスタイルと呼ぶのか分かりませんか?

名前でパラメーターを示したい場合は、ブーストパラメーターライブラリを使用できますが、慣れていないユーザーを混乱させる可能性があります。

構造体メンバーの並べ替えは、関数パラメーターの並べ替えに似ています。このようなリファクタリングは、慎重に行わないと問題が発生する可能性があります。


7
私はそれがゼロ維持可能だと思うのでそれを悪いスタイルと呼びます。1年後に別のメンバーを追加するとどうなりますか?または、メンバーの順序/タイプを変更した場合はどうなりますか?それを初期化するすべてのコードは(おそらく)壊れる可能性があります。
ビットマスク

2
@bitmaskしかし、名前付き引数がない限り、コンストラクターの呼び出しも更新する必要があります。コンストラクターが保守不可能な悪いスタイルだと考える人は多くないと思います。また、名前付き初期化はCではなくC99であると思います。そのC ++は明らかにスーパーセットではありません。
Christian Rau、

2
構造体の終わりまでの年に別のメンバーを追加すると、既存のコードでデフォルトで初期化されます。それらを並べ替える場合は、既存のすべてのコードを編集する必要がありますが、何もする必要はありません。
OO Tiib

1
@ビットマスク:最初の例は、その場合も「保守不能」になります。代わりに構造体の変数の名前を変更するとどうなりますか?もちろん、すべてを置換することはできますが、名前を変更してはならない変数の名前を誤って変更してしまう可能性があります。
マイクベイリー

@ChristianRau C99はいつCではないのですか?Cはグループで、C99は特定のバージョン/ ISO仕様ではありませんか?
altendky 2014年

1

この構文はどうですか?

typedef struct
{
    int a;
    short b;
}
ABCD;

ABCD abc = { abc.a = 5, abc.b = 7 };

Microsoft Visual C ++ 2015とg ++ 6.0.2でテストされました。正常に動作しています。
変数名の重複を避けたい場合にも、特定のマクロを作成できます。


clang++3.5.0-10 -Weverything -std=c++1zはそれを確認しているようです。しかし、それは正しくありません。標準がこれが有効なC ++であることを確認する場所を知っていますか?
ビットマスク

わかりませんが、古くからさまざまなコンパイラで使用しており、問題は発生していません。現在g ++ 4.4.7でテストされています-正常に動作します
2013年

5
私はこの仕事を考えていません。お試しくださいABCD abc = { abc.b = 7, abc.a = 5 };
raymai97 2017

@deselect、これは、フィールドがoperator =によって返される値で初期化されるため、機能します。したがって、実際にはクラスメンバーを2回初期化します。
Dmytro Ovdiienko

1

私にとって、インライン初期化を許可する最も怠惰な方法は、このマクロを使用することです。

#define METHOD_MEMBER(TYPE, NAME, CLASS) \
CLASS &set_ ## NAME(const TYPE &_val) { NAME = _val; return *this; } \
TYPE NAME;

struct foo {
    METHOD_MEMBER(string, attr1, foo)
    METHOD_MEMBER(int, attr2, foo)
    METHOD_MEMBER(double, attr3, foo)
};

// inline usage
foo test = foo().set_attr1("hi").set_attr2(22).set_attr3(3.14);

そのマクロは属性と自己参照メソッドを作成します。

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