C++索引类成员

6
我有一个向量类。
class vector3f
{
public:
float x;
float y;
float z;
};

如果我想要访问其中一个元素,我需要输入 vec.x、vec.y 和 vec.z。现在,我有一个算法应该按索引访问数据成员,例如:
for(int i = 0; i < 3; i++)
{
vec[i] = i*i;
}

没有索引来处理这个问题的话,需要编写多个if条件语句:

for(int i = 0; i < 3; i++)
{
if(i == 0){vec.x = i*i;}
else if(i == 1){vec.y = i*i;}
else if(i == 2){vec.z = i*i;}
}

有没有一种方法可以索引数据成员,使它们在编译时解析? 我不想在向量类中使用数组,因为使用vec.x、vec.y和vec.z访问数据成员对程序员更方便。避免这些if条件语句的最佳解决方案是什么?


1
你知道你可以重载数组索引operator[]吗? - Some programmer dude
我认为使用带有switch语句的重载operator[]将被优化并内联。此外,如果您需要这样的功能,为什么不使用vector<float>array<float,3>呢? - luk32
@luk32 优化:我同意。使用std类:也许是因为它们不提供.x访问? - Angew is no longer proud of SO
3个回答

7
你可以为你的类重载operator[],将那些if条件(最好是一个switch)本地化到一个地方:
class vector3f
{
public:
  float x;
  float y;
  float z;

  float& operator[] (size_t i)
  {
    switch (i) {
      case 0: return x;
      case 1: return y;
      case 2: return z;
      default: throw "something";
    }
  }

  float operator[] (size_t i) const
  { return (*const_cast<vector3f*>(this))[i];
}

这是一种通用且安全的实现方式。 如果你的类实际上是一个标准布局类1,就像你展示的那样,而且如果你知道编译器/平台的对齐和填充方案,并且你从中知道float会在内存中结构化排列,那么你也可以像这样实现运算符:
float& operator[] (size_t i)
{
  return (&x)[i];
}

这将把x视为float数组的起始位置。它会更快,但依赖于我上面提到的if语句,并且不进行边界检查。


1 这基本意味着它没有非空的基类,没有virtual函数,并且没有非标准布局类型的数据成员。


我认为你在 "if" 段落中所说的类型是 Plain-old-data type。我也不认为你需要了解填充或 ABI。如果它是一个 POD,那么它保证可以工作。我认为在这里不需要二进制兼容性。 - luk32
完美,这正是我要找的! - user3067395
那么union呢?类似于union{ struct{ float x, y, z; }; float i[3]; }这样的东西。 - Quest
1
@Quest 相同的对齐/填充注意事项适用,此外,在联合体中写入一个成员然后读取另一个成员在技术上是未定义行为。 - Angew is no longer proud of SO
@Angew 我读了一篇 GCC 文档(如此回答中所发布的),文档将其列在 实现定义行为 下。 - Quest
显示剩余2条评论

1
不是绝对在编译时,但你可以进行优化。
你可以使用指向成员变量的指针,然后再使用它的数组。从以下开始:
float vector3f::*px;
px = &vector3f::x;
(vec.*px) = 10.2;

您可以这样翻译:您随后声明了指向成员的数组:
float vector3f::*pArray[3];

分配它们:
pArray[0] = &vector3f::x;
pArray[1] = &vector3f::y;
pArray[2] = &vector3f::z;

使用它们:
(vec.*pArray[0]) = 10.2; // This is vector3f::x
(vec.*pArray[1]) = 1042.5; // This is vector3f::y

适当改变循环体以索引适当的成员指针!


0

使用参考资料可以得到一个有趣的替代方案。

class vector3f
{
public:
  float &x, &y, &z;
  float value[3];

  vector3f() :
    x(value[0]), y(value[1]), z(value[2]){}
};

这里我们将x绑定到value[0],y绑定到value[1],z绑定到value[2] - 它们是数组中条目的别名,但需要注意的是它们在类中每个引用会分配额外的4个字节。通过这种方式,我们可以为用户方便地命名数组。

我们还可以避免对重载的operator [进行运行时调用(但好的编译器应该可以在一个switch/case上内联访问)。

使用示例:

vector3f test;
test.value[0] = 227.f;
test.value[1] = 42.f;
test.value[2] = -1.f;
printf("Values are %lf, %lf and %lf.",test.x,test.y,test.z);

输出结果:

Values are 227.000000, 42.000000 and -1.000000.

1
问题在于这将几乎使结构的大小翻倍,因为编译器必须为引用成员(可能在内部实现为指针)分配空间。而且它将要求作者手动提供复制/移动构造函数和赋值运算符,因为默认值不再有效。 - Angew is no longer proud of SO
@user3067395 是的,你必须自己提供它们(请参见我在此处的第一条评论)。实际上,我相信复制构造函数仍然可以默认,只是会做错事情(将它们初始化为对象的数组)。 - Angew is no longer proud of SO
@Angew 嗯,看起来答案最多只能算是含糊不清。请参考这篇帖子。引用变量可能会占用运行时内存,也可能不会。我并不确定sizeof返回的是编译后的真实大小,还是类中每个成员的大小。标准规定引用的sizeof等于所引用类型的sizeof,因此这可能会导致字节加倍。sizeof引用返回0是没有意义的。 - Pcube
@Angew... 确信了! :) 我不得不重新读了几遍。我在逻辑上有些困惑,认为引用与其所在类中的成员不需要更多的空间,虽然这是正确的,但 5.3.3/2 确实说明了 sizeof 对类的影响。感谢您的耐心。 - Pcube
1
这样做:union{ struct{ float x, y, z; }; float i[3]; } 将导致 sizeof(union) = 12bytes。这比您的示例中的24小。 - Quest
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接