2016-10-08 2 views
3

Я пишу код, который передает много многопараметрических переменных. Например, я мог бы передать «ориентацию», которая составляет шесть двухместных (три декартовых координаты и три поворота вокруг осей). Было бы разумно определить структуру 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]); 
} 

Очевидно, что это чревато ошибками. У меня есть три потенциальных решения, с одним из любимых.

Решение 1

можно использовать #define или константные глобалам, в заголовке где-то, чтобы имена псевдонимов для индексов.

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 всегда последовательны, но загрязняют глобальное пространство имен. Я мог бы скрыть это в пространстве имен, но тогда это более неудобно использовать.

Решение 2

Идея упоминалось ранее here:

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++ STRUCT упаковки. I думаю, что это безопасно, пока Orientation является POD. Я нервничаю из-за этого.

Раствор 3

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); 
} 

Это больше не опирается на правила-структуры упаковки, и имеет приятный синтаксис. Однако он полагается на compiler optimizations, чтобы гарантировать, что он имеет нулевые служебные данные.

Solution 4

на основе this comment по 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)); 
} 

Вопросы

  1. Solution 3 кажется, что лучше для меня. Имеет ли потенциальный недостаток то, что Мне не хватает, за исключением оптимизации компилятора?

  2. Есть ли другое решение, превосходящее любой из этих трех?

+0

Является ли использование индексов действительно ошибкой? – GManNickG

+0

В большинстве случаев, нетрудно получить правильные показатели. Но фактические параметры немного сложнее, чем мои примеры. Кроме того, математика более сложна. Устранение неполадок намного проще, когда вы просматриваете матричные умножения, которые используют 'orient.x' вместо' p [0] '. – swarn

+2

Что-то рассмотреть - это просто свободная функция: 'double x (const double * const p) {return p [0]; } 'и' x (p) '. – GManNickG

ответ

1

Во-первых, я бы не стал дисконтировать только копирование параметров из массива в простую структуру. Копирование 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 Я сомневаюсь, что компилятор может оптимизировать размер вашей Orientation структуры, она будет иметь размер должен содержать 6 ссылок. В решении 3 он не может быть назначен из-за ссылок.

+0

Проверьте ссылку, которую я разместил в решении 3. Компиляция с помощью g ++ с использованием '-O3' полностью исключает структуру: чтение/запись из членов структуры заменяется непосредственно доступом к массиву. – swarn

+0

@swarn ОК, я стою исправлено! Он никогда не перестает удивлять меня, на что способны оптимизаторы. –

+0

Это довольно хороший ответ. Храните данные в виде массива и используйте функции доступа. –

1

Не пытайтесь перегруппировать массив парных элементов как структуру. Конечно, это сработает, но это небезопасно, и это сбивает с толку. Функциональная подпись отлично определена, она принимает массив из шести двухместных номеров, из которых первые три являются 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, , то только Megacorp может принять решение перейти на кватернионы, а , вероятно, только в официальном выпуске новой версии API. Если вы написали код самостоятельно, предположительно вы сами решили на Euler углы, а скорее кватернионы, и вы можете даже изменить представление в ответ на этот ответ.

Это настоящая проблема. Поскольку вы убрали информацию о типе, компилятор не может вам помочь, поэтому вам нужно запланировать его нарушение.

+0

Хороший ответ. Вы правы, что, уменьшая параметры до массива удвоений, информация о типе теряется, и есть только так много можно сделать. – swarn

+0

Это означает, что если я сконструирую 'Orientation' из параметра' double * p', система типов не сохранит меня, если я передам массив двойников, который был определен с использованием матрицы вращения. Но, по крайней мере, он может убедиться, что 'x' обращается последовательно для всех« Ориентаций », и немного легче понять, что я ожидал« Ориентация », когда вызывающая функция передавала« МатриксОриентация »в двойную * p'. – swarn

+0

OP, похоже, хочет микро-оптимизировать. В то время как мое решение прекрасно подходит для конкретной проблемы, оно не будет масштабироваться до больших структур и сложных алгоритмов, которые все еще подавляют копии (но насколько вы выиграете от микрооптимизации?) –