Я пишу код, который передает много многопараметрических переменных. Например, я мог бы передать «ориентацию», которая равна шести двойным (три декартовы координаты и три поворота вокруг осей). Было бы разумно определить структуру Orientation и использовать ее в качестве типа аргумента. Однако из-за ограничений API эти параметры должны храниться как и часто передаваться в функции как указатели на массивы параметров:
// The sane version, using a struct
double distance_from_origin(const Orientation & o) {
return sqrt(o.x * o.x + o.y * o.y + o.z * o.z);
}
// The version I must write due to API constraints
double distance_from_origin(const double * const p) {
return sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
}
Очевидно, что это подвержено ошибкам. У меня есть три возможных решения, с одним из них.
Я могу использовать #define или const globals где-то в заголовке, чтобы использовать псевдонимы для индексов.
const size_t x = 0;
const size_t y = 1;
const size_t z = 2;
double distance_from_origin(const double * const p) {
return sqrt(p[x] * p[x] + p[y] * p[y] + p[z] * p[z]);
}
Это гарантирует x
всегда согласован, но загрязняет глобальное пространство имен. Я мог бы скрыть это в пространстве имен, но тогда это было бы более неудобно для использования.
Идея, упомянутая ранее Вот:
struct Orientation {double x, y, z, rot_x, rot_y, rot_z};
Orientation& asOrientation(double * p) {
return *reinterpret_cast<Orientation*>(p);
}
double distance_from_origin(const double * const p) {
Orientation& o = asOrientation(p)
return sqrt(o.x * o.x + o.y * o.y + o.z * o.z);
}
Это имеет более приятный синтаксис, но опирается на правила структурной упаковки C / C ++. я считать что это безопасно, пока Orientation
это стручок Я нервничаю, полагаясь на это.
struct Orientation {
Orientation(double * p): x{p[0]}, y{p[1]}, z{p[2]}, rot_x{p[3]},
rot_y{p[4]}, rot_z{p[5]} {}
double &x, &y, &z, &rot_x, &rot_y, &rot_z
};
double distance_from_origin(const double * const p) {
Orientation o{p};
return sqrt(o.x * o.x + o.y * o.y + o.z * o.z);
}
Это больше не зависит от правил упаковки структуры и имеет приятный синтаксис. Тем не менее, он опирается на оптимизация компилятора чтобы убедиться, что у него нет накладных расходов.
На основе этот комментарий GManNickG.
constexpr double& x(double * p) {return p[0];}
constexpr double& y(double * p) {return p[1];}
constexpr double& z(double * p) {return p[2];}
// ... etc.
double distance_from_origin(const double * const p) {
return sqrt(x(p) * x(p) + y(p) * y(p) + z(p) * z(p));
}
Решение 3 кажется мне лучшим. Есть ли у него потенциальный недостаток, который
Я скучаю, не полагаясь на оптимизацию компилятора?
Есть ли другое решение, которое превосходит любое из этих трех?
Во-первых, я бы не стал сбрасывать со счетов просто копирование параметров из массива в простую структуру. Копирование 6 дублей в структуру будет очень быстрым.
В противном случае я предлагаю обернуть массив в класс и выставить параметры как функции-члены:
class Orientation {
const double *p_;
public:
Orientation(const double *p) : p_(p) {}
double x() const { return p_[0]; }
double y() const { return p_[1]; }
double z() const { return p_[2]; }
double rot_x() const { return p_[3]; }
double rot_y() const { return p_[4]; }
double rot_z() const { return p_[5]; }
};
С вашим решением 3 я сомневаюсь, что компилятор может оптимизировать размер вашего С Решением 3 его нельзя назначить из-за ссылок.Orientation
структура, она будет иметь размер, содержащий 6 ссылок.
Не пытайтесь переосмыслить массив значений типа double как структуру. Конечно, это будет работать, но это не безопасно, и это сбивает с толку. Сигнатура функции совершенно четко определена, она принимает массив из шести двойников, из которых первые три — это x, y и z, а вторые три — углы Эйлера. Так что это ваш интерфейс.
/*
get distance of an orientation from an origin
Params: p[0] = x, p[1] = y, p[2] = z, p[4].p[4],p[6] Euler angles (unused)
Returns: Euclidean distance of x,y,z from origin.
*/
double distance_for_origin(const double *p);
Здесь нет проблем, звонить немного сложно, но это то, что нужно, чтобы жить
с.
Теперь, как это реализовать? У вас есть несколько вариантов, в зависимости от того, сколько
из этих структур «ориентации» у вас есть в коде. Если вы только
один или два
double distance_from_origin(const double *p)
{
double x = p[0];
double y = p[1];
double z = p[2];
return sqrt(x*x + y*y + z*z);
}
Это хорошо, даже самый худший оптимизатор в мире оптимизирует назначения, если по какой-то причине у него заканчиваются регистры.
Вопрос, однако, в том, когда и где этот интерфейс будет
перерыв? Допустим, мы идем к описанию ориентации с кватернионами.
Теперь, конечно, у вас будет семь двойных, x, y, z и нормальный вектор, и
угол вокруг этого нормального вектора. Вполне вероятно, что кто-то захочет
сделать это.
Но в этом случае, кто будет принимать это решение, и каков процесс
для обновления кода? Если вы используете API, предоставленный Megacorp,
тогда только Мегакорп может принять решение перейти на кватернионы, и
вероятно, только в официальном выпуске новой версии API. Если
Вы сами написали код, предположительно, вы сами определились с Эйлером
углы скорее кватернионов, и вы могли бы даже изменить представление
в ответ на этот ответ.
Это реальная проблема. Потому что вы удалили информацию о типе,
компилятор не может вам помочь, поэтому вам нужно спланировать его нарушение.