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