Самый простой способ закодировать функтор отображения structarray в C ++

StackOverflow https://stackoverflow.com/questions/1344840

Вопрос

Это опрос мнений о наиболее удобочитаемом способе что-либо сделать - использовать ли указатель 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 ++), и он надежен, но только кажется неприглядным.

Шаблонизированный Функтор

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 способ делать что-то, но он выглядит как целая куча дополнительных типизаций и синтаксиса, а также импровизированных определений классов.Он компилируется почти в точности так же, как два приведенных выше, но он также сбрасывает кучу избыточных определений функций по всему исполняемому файлу.(Я проверил это с помощью /Факсы, но, возможно, компоновщик снова удаляет их.)


Все три вышеперечисленных варианта будут работать, и компилятор выдает практически одинаковый код для всех них;итак, самый важный выбор, который я должен сделать, это просто который наиболее удобочитаем.А ты как думаешь?

Это было полезно?

Решение

Если ваша команда состоит из достаточно умных людей, я бы посоветовал доверять им и их способностям и использовать технически предпочтительное решение, которое предлагает синтаксис указателя на элемент.Это именно то, для чего он был создан.

Если вы действительно обеспокоены, вы можете предпринять некоторые шаги, чтобы облегчить любые будущие проблемы, путем

  • Отметив в комментарии рядом с typedef и использованием, что это называется синтаксисом "указатель на участника", чтобы другие члены команды знали, что искать
  • Укажите на это явно в обзоре кода, где многие из них должны присутствовать.Предложите изменить его, если он покажется вам непонятным или слишком неясным для обслуживания.

Два других подхода имеют проблемы, как описанные вами, так и за их пределами:

  • Оба требуют больше кода, имеют больше места для опечаток и т.д.
  • В offsetof примитив ограничен в том, к каким типам он может быть применен:

    Из-за расширенной функциональности структур в C ++, в этом языке использование offsetof ограничено "типами POD", что для классов более или менее соответствует концепции struct в C (хотя непроизводные классы с только общедоступными невиртуальными функциями-членами и без конструктора и / или деструктора также квалифицировались бы как POD).

От здесь.

Другие советы

Я нахожу Шаблонный Функтор очень ясным в этом случае.

ReadingAtTime<WindSelector>( 12.6f, data );

Более похожим на 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 .

Для простых вещей я бы предпочел решение с указателем на элемент.Однако у функторного подхода есть два возможных преимущества:

  1. отделение алгоритма от данных позволяет вам использовать алгоритм для большего количества задач в будущем, sine он работает с чем угодно при условии, что вы сможете построить правильный функтор.

  2. что касается # 1, это может упростить тестирование алгоритма, поскольку у вас есть способ предоставить тестовые данные для функции, которая не требует создания полных объектов данных, которые вы собираетесь использовать.Вы можете использовать более простые макетные объекты.

Однако я думаю, что функторный подход того стоит, только если функция, которую вы создаете, очень сложная и / или используется во многих разных местах.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top