私はこのようなデータ構造を持っています:
struct foo { int id; int route; 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にしたい(「dont care」の秘密コード)
マクロおよび/または関数(すでに提案されているように)は機能しますが(そして他の肯定的な効果(つまりデバッグフック)を持つかもしれません)、それらは必要以上に複雑です。最も単純で、おそらく最もエレガントな解決策は、変数の初期化に使用する定数を定義することです。
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
のどのフィールドを明示的に設定し、設定しないフィールドを(安全に)無視するかは非常に明確です。
シークレットの特別な値を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);
<stdarg.h>
を使用すると、可変個の関数(printf()
などの不特定数の引数を受け入れる)を定義できます。引数の任意の数のペアを取る関数を定義します。1つは更新するプロパティを指定し、もう1つは値を指定します。 enum
または文字列を使用して、プロパティの名前を指定します。
代わりに、プリプロセッサマクロ定義の使用を検討してください。
#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主義です。これは、行をブロックとしてまとめておくための素敵な(安全な)方法です。後で範囲検証チェックなど、マクロにさらに追加する必要がある場合、if/elseステートメントなどのようなものを壊すことを心配する必要はありません。
これは、あなたが示した要件に基づいて私がすることです。このような状況は、pythonをたくさん使い始めた理由の1つです。デフォルトパラメータの処理などは、Cを使用した場合よりもはるかに簡単になります(これはpythonプラグです、申し訳ありません;-)
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」を確認する必要はありません。
次のようなものはどうですか:
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++では、同様のパターンの名前は、今は覚えていません。
EDIT:これは Named Parameter Idiom と呼ばれます。
Gobjectが使用するパターンの1つは可変引数関数であり、各プロパティの列挙値です。インターフェイスは次のようになります。
update (ID, 1,
BACKUP_ROUTE, 4,
-1); /* -1 terminates the parameter list */
Varargs関数の作成は簡単です- http://www.eskimo.com/~scs/cclass/int/sx11b.html を参照してください。キー->値のペアを一致させ、適切な構造属性を設定するだけです。
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
here の議論に続いて、この方法でデフォルト値を定義することもできます。
#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 answer 、のように使用します。ここではメンテナンスが簡単です。つまり、構造体メンバーの数を変更すると、foo_DONT_CARE
。 最後の「偽の」コンマは許容されます に注意してください。
この問題 に対処しなければならなかったときに、X-Macrosの概念を最初に学びました。
構造体に追加される新しいフィールドに非常に柔軟です。異なるデータ型がある場合、データ型に応じて異なるdont_care
値を定義できます。 here から、2番目の例で値を出力するために使用される関数からインスピレーションを得ることができます。
All int
構造体でよければ、LIST_OF_foo_MEMBERS
からデータ型を省略し、構造体定義のX関数を#define X(name) int name;
に変更するだけです。
構造体が錆びているので、おそらくいくつかのキーワードが欠けています。しかし、デフォルトが初期化されたグローバル構造で開始し、それをローカル変数にコピーしてから変更してみませんか?
次のような初期化子:
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
最もエレガントな方法は、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が構造体のフィールドの有効な値である場合、これはあまり実行できない可能性があります。