通过名称访问数组元素

Accessing array elements by name

本文关键字:访问 数组元素      更新时间:2023-10-16

我正在编写传递大量多参数变量的代码。例如,我可能传递一个"方向",它是六个双精度(三个笛卡尔坐标和围绕轴的三次旋转)。定义一个朝向结构体并将其用作参数类型是合理的。然而,由于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 h1> 面提到的一个想法:
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是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);
}

不再依赖于结构打包规则,并且有很好的语法。然而,它依赖于编译器优化来确保它的开销为零。

<标题>解决方案4

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

  2. 有没有比这三个更好的解决方案?

首先,我不会拒绝将参数从数组复制到一个简单的结构体中。将6个双精度对象复制到一个struct中会非常快。

否则,我建议将数组包装在类中,并将形参公开为成员函数:

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,由于引用,它将无法分配。

不要试图将双精度体数组重新解释为结构体。当然它会起作用,但它不安全,而且令人困惑。函数签名是完美定义的,它接受一个包含六个双精度值的数组,其中前三个是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);
}

这很好,即使是世界上最差的优化器,如果由于某种原因寄存器耗尽,也会优化出赋值。

然而,问题是,何时何地这个接口可能休息?假设我们用四元数来描述方位。当然你会有7个双精度向量,x y z,和法向量,还有法向量上的一个角。很可能有人会想要

但在这种情况下,谁来做决定,过程是什么为了更新代码?如果您使用的是Megacorp提供的API,那么只有Megacorp可以决定使用四元数可能只在API新版本的正式发布中。如果你自己写的代码,应该是你自己决定用欧拉角度,而不是四元数,你甚至可以改变表示作为对这个答复的回应。

这才是真正的问题。因为你去掉了类型信息,编译器不能帮助你,所以你需要为它的崩溃做好准备。