按名称

时间:2016-10-08 18:32:14

标签: c++

我正在编写传递大量多参数变量的代码。例如,我可能会通过"方向,"这是六个双打(三个笛卡尔坐标和三个绕轴的旋转)。定义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 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始终保持一致,但会污染全局命名空间。我可以将其隐藏在命名空间中,但随后使用它会更加尴尬。

解决方案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 packing的规则。只要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);
}

这不再依赖于struct-packing规则,并且语法很好。但是,它依赖compiler optimizations来确保它的开销为零。

解决方案4

基于GManNickG的this comment

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. 解决方案3对我来说似乎是最好的。它有潜在的缺点吗? 我失去了,除了依赖编译器优化?

  2. 是否有其他解决方案优于这三种解决方案中的任何一种?

2 个答案:

答案 0 :(得分: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它将无法分配到期参考文献。

答案 1 :(得分:1)

不要尝试将双精度数组作为结构重新插入。当然它会起作用,但它不安全,而且令人困惑。函数签名是完美定义的,它需要六个双精度数组,其中前三个是x,y和z,后三个是欧拉角。这就是你的界面。

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

这里没问题,打电话有点难,但这就是你必须要的 用。

现在该如何实施呢?你有几个选择,取决于多少 您在代码中使用的这些“方向”结构。如果你只是 一两个

emulator64 -avd test-22 -no-skin -no-audio -no-window

这很好,即使是最糟糕的优化器,如果由于某种原因它用完了寄存器,也会优化分配。

然而,问题是这个界面何时何地可能发生 打破?假设我们用四元数描述方向。 当然,你将有七个双打,x,y,z和法线向量,和 圆形的法线向量。很可能有人会想要 这样做。

但在那种情况下,谁会做出这个决定,以及这个过程是什么 更新代码?如果您使用的是Megacorp提供的API, 然后只有Megacorp可以决定去四元数,并且 可能只是在正式发布新版本的API时。如果 你自己写了代码,大概是你自己决定使用Euler 角度而不是四元数,你甚至可以改变表示 回应这个回复。

这才是真正的问题。因为你已经删除了类型信息, 编译器无法帮助你,所以你需要计划它的破坏。