Это опрос мнений о наиболее читабельном способе сделать что-то — использовать ли указатель на член C++, смещение байта или шаблонный функтор для определения «выбрать член X из структуры foo».
У меня есть тип, который содержит большой вектор структур, и я пишу служебную функцию, которая в основном работает как 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...
}
Моя функция просто выполняет кубическую интерполяцию, чтобы угадать эти условия для некоторого заданного момента времени между доступные образцы.
// 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 );
}
Я хотел бы обобщить эту функцию, чтобы ее можно было применять к любому элементу, а не только к температуре. Я могу придумать три способа сделать это, и хотя все они просты в написании кода, я не уверен, какой из них будет наиболее удобочитаемым для тех, кто будет использовать это через год. Вот что я рассматриваю:
Синтаксис указателя на член
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++y" способ сделать это, но это выглядит странно, и весь синтаксис указателя на член используется редко и поэтому плохо понимается большинством людей в моей команде. Это технически «правильный» способ, но также и тот, о котором я получаю самые запутанные электронные письма.
Смещение структуры
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 способ ведения дел, но он выглядит как целая куча лишней типизации, синтаксиса и импровизированных определений классов. Он компилируется почти так же, как и два вышеприведенных, но также выгружает множество избыточных определений функций по всему исполняемому файлу. (Я проверил это с помощью /FAcs. , но, возможно, компоновщик снова удалит их.)
Все три вышеперечисленных варианта будут работать, и компилятор выдает почти одинаковый код для всех из них; так что самый важный выбор, который я должен сделать, это просто что будет наиболее читабельно. Что вы думаете?