これは、何かを行うための最も読みやすい方法に関する意見の投票です。C++ のメンバーへのポインター、バイト オフセット、またはテンプレート化されたファンクターを使用して「構造体 foo からメンバー X を選択する」を定義するかどうかです。
構造体の大きなベクトルを含む型があり、基本的にそれらのいくつかの範囲でreduceとして動作するユーティリティ関数を作成しています。各構造は、従属変数のグループを独立した次元に沿ったいくつかの点に関連付けます。簡単な例を考え出すために、これが時間の経過に伴う部屋の一連の環境条件を記録すると想像してください。
// all examples are psuedocode for brevity
struct TricorderReadings
{
float time; // independent variable
float tempurature;
float lightlevel;
float windspeed;
// etc for about twenty other kinds of data...
}
私の関数は、3 次補間を実行して、使用可能なサンプル間の特定の時点での条件を推測するだけです。
// performs Hermite interpolation between the four samples closest to given time
float TempuratureAtTime( float time, sorted_vector<TricorderReadings> &data)
{
// assume all the proper bounds checking, etc. is in place
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time,
data[idx-1].time, data[idx-1].tempurature,
data[idx+0].time, data[idx+0].tempurature,
data[idx+1].time, data[idx+1].tempurature,
data[idx+2].time, data[idx+2].tempurature );
}
この関数を一般化して、温度だけでなく、任意のメンバーに一般的に適用できるようにしたいと思います。これを行うには 3 つの方法が考えられます。どれも簡単にコーディングできますが、1 年後にこれを使用する必要がある人にとって、どの方法が最も読みやすいかはわかりません。ここに私が考えているものがあります:
メンバーへのポインターの構文
typedef int TricorderReadings::* selector;
float ReadingAtTime( time, svec<TricorderReadings> &data, selector whichmember )
{
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time, data[idx-1].time, data[idx-1].*whichmember,
/* ...etc */ );
}
// called like:
ReadingAtTime( 12.6f, data, &TricorderReadings::windspeed );
これは最も "C++ らしい" 方法のように感じますが、奇妙に見えます。また、メンバーへのポインター構文全体がほとんど使用されないため、私のチームのほとんどの人はほとんど理解していません。これは技術的には「正しい」方法ですが、私が最も混乱するメールを受け取る方法でもあります。
構造物のオフセット
float ReadingAtTime( time, svec<TricorderReadings> &data, int memberoffset )
{
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time,
data[idx-1].time,
*(float *) ( ((char *)(&data[idx-1]))+memberoffset ),
/* ...etc */ );
}
// called like:
ReadingAtTime( 12.6f, data, offsetof(TricorderReadings, windspeed) );
これは機能的には上記と同じですが、ポインター計算を明示的に行います。このアプローチは、私のチームの全員 (全員が C++ の前に C を学んだ) にすぐに馴染み、理解できるものになるでしょう。
テンプレート化されたファンクター
template <class F>
float ReadingAtTime( time, svec<TricorderReadings> &data )
{
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time,
data[idx-1].time,
F::Get(data[idx-1]) ),
/* ...etc */ );
}
// called with:
class WindSelector
{
inline static float Get(const TricorderReadings &d) { return d.windspeed; }
}
ReadingAtTime<WindSelector>( 12.6f, data );
これは、最も単純で STL っぽい方法ですが、余分な型付けと構文、即席のクラス定義が大量にあるように思えます。上記の2つとほぼ同じものにコンパイルされますが、実行可能ファイル全体に冗長な関数定義の束もダンプされます. (私はこれを/FAcsで確認しましたが、リンカーがそれらを再び取り出す可能性があります。)
上記の 3 つすべてが機能し、コンパイラはそれらすべてに対してほぼ同じコードを発行します。したがって、私がしなければならない最も重要な選択は、単純にどちらが最も読みやすいかということです。どう思いますか?