3

これは、何かを行うための最も読みやすい方法に関する意見の投票です。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 つすべてが機能し、コンパイラはそれらすべてに対してほぼ同じコードを発行します。したがって、私がしなければならない最も重要な選択は、単純にどちらが最も読みやすいかということです。どう思いますか?

4

4 に答える 4

3

この場合、テンプレート化されたファンクターは非常に明確です。

ReadingAtTime<WindSelector>( 12.6f, data );
于 2009-08-28T03:21:09.117 に答える
2

より STL っぽい方法は、関数呼び出しのように見えるメンバーへのポインターを介したアクセスを行うジェネリック ファンクターです。次のようになります。

#include <functional>

template <class T, class Result>
class member_pointer_t: public std::unary_function<T, Result>
{
    Result T::*member;
public:
    member_pointer_t(Result T::*m): member(m) {}
    Result operator()(const T& o) const { return o.*member; }
};

template <class T, class Result>
member_pointer_t<T, Result> member_pointer(Result T::*member)
{
    return member_pointer_t<T, Result>(member);
}

float ReadingAtTime( float time, const std::vector<TricorderReadings> &data, member_pointer_t<TricorderReadings, float> f )
{
   int idx = FindClosestSampleBefore( time, data );
   return CubicInterp( time, data[idx-1].time, f(data[idx-1]));
}

ReadingAtTime( 12.6f, data, &TricorderReadings::windspeed);

この例には、ファンクターのテンプレート引数を推測するのに役立つヘルパー関数も含まれています (この例では使用されていません)。

関数 ReadingAtTime は、テンプレート化されたファンクターも受け入れる場合があります。

template <class Func>
float ReadingAtTime( float time, const std::vector<TricorderReadings>& data, Func f);

ReadingAtTime( 12.6f, data, member_pointer(&TricorderReadings::windspeed));

このようにして、すべての種類の関数/ファンクターを使用して、メンバーへのポインターだけでなく、data[idx - 1] から値を取得できます。

member_pointer のより一般的な同等物は、std::tr1::bind または std::tr1::mem_fn かもしれません。

于 2009-08-28T09:58:12.800 に答える
1

あなたのチームがかなり賢い人で構成されている場合は、彼らとその能力を信頼し、ポインターからメンバーへの構文が提供する技術的に好ましいソリューションを使用することをお勧めします。これは、そのようなもののために作られました。

本当に心配な場合は、将来の問題を軽減するためにいくつかの措置を講じることができます。

  • typedef の近くのコメントと、これが「メンバーへのポインター」構文と呼ばれる使用法に注意してください。これにより、他のチームメンバーが何を調べればよいかがわかります。
  • それらの多くが存在するはずのコードレビューで明示的に指摘してください。理解できない、または保守するには不明瞭すぎると思われる場合は、変更を申し出てください。

あなたが説明したように、他の2つのアプローチには問題があります。

  • どちらもより多くのコードが必要であり、タイプミスなどの余地があります。
  • プリミティブは、offsetof適用できるタイプが制限されています。

    C++ では構造体の機能が拡張されているため、この言語では、offsetof の使用は「POD 型」に制限されています。これは、クラスの場合、多かれ少なかれ C の構造体の概念に対応します (ただし、public non のみを持つ非派生クラス-virtual メンバ関数で、コンストラクタやデストラクタがないものも POD として認定されます)。

ここから。

于 2009-08-28T03:22:54.870 に答える