C構造体のデフォルト値


92

私はこのようなデータ構造を持っています:

    struct foo {
        int id;
        intルート;
        int backup_route;
        int current_route;
    }

そしてupdate()と呼ばれる関数を使用して、その変更をリクエストします。

  update(42、dont_care、dont_care、new_route);

これは非常に長く、構造に何かを追加する場合、update(...)へのすべての呼び出しに 'dont_care'を追加する必要があります。

代わりに構造体を渡すことを考えていますが、構造体に「dont_care」をあらかじめ入力しておくことは、関数呼び出しで単純にスペルアウトするよりも面倒です。構造体をどこかにデフォルト値dont careで作成し、それをローカル変数として宣言した後に、気になるフィールドを設定することはできますか?

    struct foo bar = {.id = 42、.current_route = new_route};
    update(&bar);

表現したい情報だけを更新関数に渡す最もエレガントな方法は何ですか?

そして、私は他のすべてをデフォルトで-1にしたい(「気にしない」の秘密のコード)

回答:


155

マクロや関数(すでに提案したとおり)は機能しますが(他のプラスの効果(つまり、デバッグフック)もあるかもしれません)、必要以上に複雑です。最も単純でおそらく最もエレガントなソリューションは、変数の初期化に使用する定数を定義することです。

const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something
    dont_care, dont_care, dont_care, dont_care
};
...
struct foo bar = FOO_DONT_CARE;
bar.id = 42;
bar.current_route = new_route;
update(&bar);

このコードには、間接参照を理解するというオーバーヘッドはほとんどありません。また、bar明示的に設定したフィールドのうち、設定していないフィールドを(安全に)無視しているフィールドは非常に明確です。


6
このアプローチのもう1つのメリットは、C99の機能に依存しないことです。
D.Shawley

24
これに変更すると、500行がプロジェクトから「脱落」しました。これに2回賛成票を投じたいと思います。
アーサーウルフェルト

4
PTHREAD_MUTEX_INITIALIZERこれも使用します。
2012年

構造体に存在する要素の数に柔軟であるためにX-Macrosに依存する以下の回答を追加しまし
アントニオ

15

シークレットスペシャル値を0に変更し、Cのデフォルトの構造体メンバーセマンティクスを利用できます。

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

次に、初期化子で指定されていないbarのメンバーとして0を渡します。

または、デフォルトの初期化を行うマクロを作成することもできます。

#define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ }

struct foo bar = FOO_INIT( .id = 42, .current_route = new_route );
update(&bar);

FOO_INITは機能しますか?同じメンバーを2回初期化すると、コンパイラーは文句を言うべきだと思います。
ジョナサンレフラー、

1
私はgccで試してみましたが、文句はありませんでした。また、標準ではこれに反することは何も見つかりませんでした。実際、上書きが具体的に言及されている例が1つあります。
jpalecek 2009

2
C99:open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf:6.7.8.19:初期化はイニシャライザリストの順序で行われ、各初期化子は特定のサブオブジェクトに提供され、以前にリストされた初期化子をオーバーライドしますサブオブジェクト;
altendky

2
これがtrueの場合、すべてのイニシャライザを含むDEFAULT_FOOを定義できず、次にstruct foo bar = {DEFAULT_FOO、.id = 10};を実行できませんでした。?
ジョン

7

<stdarg.h>可変個の関数を定義できます(これは、などの引数を無制限に受け入れますprintf())。任意の数の引数のペアをとる関数を定義します。1つは更新するプロパティを指定し、もう1つは値を指定します。enumプロパティの名前を指定するには、または文字列を使用します。


こんにちは@Matt、enum変数をstructフィールドにマップする方法は?ハードコードしますか?その後、この関数は特定のにのみ適用されますstruct
misssprite 2014

5

おそらく、代わりにプリプロセッサマクロ定義の使用を検討してください。

#define UPDATE_ID(instance, id)  ({ (instance)->id= (id); })
#define UPDATE_ROUTE(instance, route)  ({ (instance)->route = (route); })
#define UPDATE_BACKUP_ROUTE(instance, route)  ({ (instance)->backup_route = (route); })
#define UPDATE_CURRENT_ROUTE(instance, route)  ({ (instance)->current_route = (route); })

(struct foo)のインスタンスがグローバルである場合、もちろんそのためのパラメーターは必要ありません。しかし、私はあなたがおそらく複数のインスタンスを持っていると仮定しています。({...})ブロックの使用は、GCCに適用されるGNU-ismです。これは、行をブロックとしてまとめるのに便利な(安全な)方法です。後で範囲検証チェックなどのマクロをさらに追加する必要がある場合は、if / elseステートメントなどを壊すことを心配する必要はありません。

これは、あなたが示した要件に基づいて私が行うことです。このような状況は、私がpythonをよく使い始めた理由の1つです。デフォルトのパラメーターなどの処理は、Cを使用する場合よりもはるかに簡単になります(これはpythonプラグだと思います。


4

gobjectが使用するパターンの1つは可変関数であり、各プロパティの列挙値です。インターフェイスは次のようになります。

update (ID, 1,
        BACKUP_ROUTE, 4,
        -1); /* -1 terminates the parameter list */

varargs関数の記述は簡単です-http://www.eskimo.com/~scs/cclass/int/sx11b.htmlを参照してください。キーと値のペアを照合して、適切な構造属性を設定するだけです。


4

この構造体はupdate()関数にのみ必要であるように見えるので、この構造体をまったく使用しないでください。構造体の背後にある意図が不明瞭になるだけです。これらのフィールドを変更および更新する理由を再考し、この「小さな」変更のために個別の関数またはマクロを定義する必要があります。

例えば


#define set_current_route(id, route) update(id, dont_care, dont_care, route)
#define set_route(id, route) update(id, dont_care, route, dont_care)
#define set_backup_route(id, route) update(id, route, dont_care, dont_care)

または、すべての変更ケースに対して関数を作成することもできます。すでにお気づきのように、すべてのプロパティを同時に変更するわけではないので、一度に1つのプロパティのみを変更できるようにします。これにより、読みやすさが向上するだけでなく、さまざまなケースの処理にも役立ちます。たとえば、現在のルートのみが変更されていることがわかっているため、すべての「dont_care」を確認する必要がありません。


一部のケースでは、同じ呼び出しでそれらの3つが変更されます。
Arthur Ulfeldt

次のシーケンスを使用できます:set_current_rout(id、route); set_route(id、route); apply_change(id); または類似。私が思うのは、すべてのセッターに対して1つの関数呼び出しを持つことができるということです。そして、後で実際の計算(またはこれまでの計算)を行います。
quinmars 2009

4

次のようなものはどうですか:

struct foo bar;
update(init_id(42, init_dont_care(&bar)));

と:

struct foo* init_dont_care(struct foo* bar) {
  bar->id = dont_care;
  bar->route = dont_care;
  bar->backup_route = dont_care;
  bar->current_route = dont_care;
  return bar;
}

そして:

struct foo* init_id(int id, struct foo* bar) {
  bar->id = id;
  return bar;
}

それに応じて:

struct foo* init_route(int route, struct foo* bar);
struct foo* init_backup_route(int backup_route, struct foo* bar);
struct foo* init_current_route(int current_route, struct foo* bar);

C ++では、似たようなパターンの名前が今は覚えていません。

編集:それは名前付きパラメータイディオムと呼ばれています。


コンストラクタと呼ばれていると思います。
不明

いいえ、私はあなたができることを意味しました:update(bar.init_id(42).init_route(5).init_backup_route(66));
Reunanen、2009

少なくともここでは、「チェーンされたSmalltalkスタイルの初期化」と呼ばれているようです。cct.lsu.edu
〜rguidry

2

私は構造体に錆びているので、ここにいくつかのキーワードがないと思います。しかし、なぜデフォルトが初期化されたグローバル構造から始めて、それをローカル変数にコピーしてから、それを変更しないのですか?

次のようなイニシャライザ:

void init_struct( structType * s )
{
   memcopy(s,&defaultValues,sizeof(structType));
}

次に、それを使用したい場合:

structType foo;
init_struct( &foo ); // get defaults
foo.fieldICareAbout = 1; // modify fields
update( &foo ); // pass to function

2

X-Macroで問題に対処できます

構造体の定義を次のように変更します。

#define LIST_OF_foo_MEMBERS \
    X(int,id) \
    X(int,route) \
    X(int,backup_route) \
    X(int,current_route)


#define X(type,name) type name;
struct foo {
    LIST_OF_foo_MEMBERS 
};
#undef X

そして、すべてのフィールドをに設定する柔軟な関数を簡単に定義できますdont_care

#define X(type,name) in->name = dont_care;    
void setFooToDontCare(struct foo* in) {
    LIST_OF_foo_MEMBERS 
}
#undef X

ここでの議論に続いて、この方法でデフォルト値を定義することもできます:

#define X(name) dont_care,
const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo };
#undef X

これは次のように変換されます:

const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};

そして、のようにそれを使用hlovdal答えここでのメンテナンスが容易になり、すなわち構造体のメンバの数を変更すると自動的に更新されますことを利点とfoo_DONT_CARE最後の「偽の」コンマは受け入れられることに注意しください。

この問題に取り組む必要があったとき、私は最初にX-Macrosの概念を学びました。

構造体に追加される新しいフィールドに非常に柔軟です。データ型が異なる場合dont_careは、データ型に応じて異なる値を定義できます。ここから、2番目の例で値を出力するために使用される関数からヒントを得られます。

all int構造体で問題がなければ、データ型を省略してLIST_OF_foo_MEMBERS、構造体定義のX関数を次のように変更するだけです。#define X(name) int name;


この手法は、Cプリプロセッサがレイトバインディングを使用することを利用しており、単一の場所で何か(たとえば、状態)を定義し、使用されたアイテムが常に同じ順序であるすべての場所に新しいアイテムがあることを100%確認する場合に非常に役立ちます。自動的に追加され、削除されたアイテムは削除されます。のような短い名前を使用したりX、通常_ENTRYはリスト名に追加したりすることはあまり好きではありません#define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ...
hlovdal 2017

1

最もエレガントな方法は、update()関数を使用せずに構造体フィールドを直接更新することです。ただし、問題に出くわさない、それを使用する正当な理由があるかもしれません。

struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;

または、Pukkuが提案したように、構造体のフィールドごとに個別のアクセス関数を作成できます。

それ以外の場合、私が考えることができる最良の解決策は、構造体フィールドの「0」の値を「更新しない」フラグとして扱うことです。そのため、関数を作成してゼロ化された構造体を返し、これを使用して更新します。

struct foo empty_foo(void)
{
    struct foo bar;
    bzero(&bar, sizeof (struct bar));
    return bar;    
}

struct foo bar = empty_foo();
bar.id=42;
bar.current_route = new_route;
update(&bar);

ただし、0が構造体のフィールドの有効な値である場合、これはあまり現実的ではありません。


ネットワークは正当な理由です=)しかし、-1が「気にしない」値である場合、empty_foo()関数を再構築し、そのアプローチを使用しますか?
gnud

申し訳ありませんが、誤って反対投票をクリックして、後で気づきました!!! 編集がない限り、今は元に戻す方法が見つかりません...
Ciro Santilli郝海东冠状病六四事件法轮功
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.