13

我很确定这是可能的,因为我很确定我已经看到它完成了。我认为这很棒,但我很乐意接受“这是一个糟糕的想法,因为____”这样的答案。

假设我们有一个基本结构。

struct vertex
{
    float x, y, z;
};

现在,我想在这些变量上实现别名。

vertex pos;
vertex col;
vertex arr;

pos.x = 0.0f; pos.y = 0.5f; pos.z = 1.0f;
col.r = 0.0f; col.g = 0.5f; col.b = 1.0f;
arr[0] = 0.0f; arr[1] = 0.5f; arr[2] = 1.0f;

理想情况下,第三种语法与数组无法区分。也就是说,如果我将arr作为参考参数发送给一个函数,该函数需要一个浮点数组来存储数据(例如许多 OpenGLglGet函数),它会正常工作。

你怎么看?可能的?可能但愚蠢?

4

13 回答 13

24

我要做的是制作访问器:

struct Vertex {
    float& r() { return values[0]; }
    float& g() { return values[1]; }
    float& b() { return values[2]; }

    float& x() { return values[0]; }
    float& y() { return values[1]; }
    float& z() { return values[2]; }

    float  operator [] (unsigned i) const { return this->values_[i]; }
    float& operator [] (unsigned i)       { return this->values_[i]; }
    operator float*() const { return this->values_; }

private:
    float[3] values_;
}
于 2009-01-30T06:12:45.947 回答
14

使用工会?

union vertex
{
    struct { float x, y, z; };
    struct { float r, g, b; };
    float arr[3];
};

我不会推荐它——它会导致混乱。


补充

正如 Adrian 在他的回答中所指出的,ISO C++ 不支持这种具有匿名结构成员的联合。它在 GNU G++ 中工作(当您打开 ' ' 时会抱怨不支持-Wall -ansi -pedantic)。这让人想起了标准之前的 C 天(K&R 第一版之前),当结构元素名称必须在所有结构中唯一时,您可以使用收缩符号来获得结构内的偏移量,并且您可以使用来自其他结构类型的成员名称 - 一种无政府状态。当我开始使用 C 时(很久以前,但在 K&R1 之后),这已经是历史用法了。

C11 (ISO/IEC 9899:2011) 支持匿名联合成员(对于两个结构)显示的符号,但早期版本的 C 标准不支持。ISO/IEC 14882:2011 (C++11) 的第 9.5 节提供匿名联合,但 GNU g++(4.9.1) 不接受用 显示的代码-pedantic,标识“ warning: ISO C++ prohibits anonymous structs [-Wpedantic]”。

由于这个想法会导致混乱,我并不特别担心它不是标准的。我不会为这项任务使用该机制(即使它是有益的,我也会对在联合中使用匿名结构持谨慎态度)。


提出了一个担忧:

这三个(xyz、rgb 和数组)不一定对齐。

它是一个由三个元素组成的联合体;这三个元素从同一个地址开始。前两个是包含 3 个浮点值的结构。没有继承,也没有虚拟函数来提供不同的布局等。结构将以连续的三个元素进行布局(实际上,即使标准允许填充)。该数组也从相同的地址开始,并且在结构中“无填充”的情况下,元素与两个结构重叠。我真的不认为会有问题。

于 2009-01-30T05:59:23.930 回答
14

联合中的无名嵌套结构不是标准 C++。但是,这应该有效:

struct Vertex
{
private:
   typedef float Vertex::* const vert[3];
   static const vert v;

public:
   typedef size_t size_type;
   float x, y, z;

   const float& operator[](size_type i) const {
      return this->*v[i];
   }

   float& operator[](size_type i) {
      return this->*v[i];
   }

};

const Vertex::vert Vertex::v = {&Vertex::x, &Vertex::y, &Vertex::z};

编辑:更多信息。该结构使用由 3 个指向数据成员的指针组成的数组来访问重载的 [] 运算符中的数据。

“typedef float Vertex::* const vert”这一行意味着 vert 是一个指向 Vertex 结构的 float 成员的指针。[3] 表示它是其中 3 个的数组。在重载的 operator[] 中,该数组被索引,指向数据成员的指针被取消引用并返回值。

此外,无论打包问题如何,此方法都应该有效 - 编译器可以随意填充 Vertex 结构,但它仍然可以正常工作。如果浮动包装不同,匿名联合将遇到问题。

于 2009-01-30T07:12:23.283 回答
4

参考?

template<typename T>
struct vertex {
    vertex() :
        r(data[0]), g(data[1]), b(data[2]),
        x(data[0]), y(data[1]), z(data[2])
    {
    }

    T *operator *() {
        return data;
    }

    const T *operator *() const {
        return data;
    }

    T data[3];
    T &r, &g, &b;
    T &x, &y, &z;
};
于 2009-01-30T13:05:23.710 回答
3

正如其他人所提到的,您可以通过工会获得这个。像这样将颜色和位置重载到相同的结构上可能不是一个好主意(例如,添加两种颜色通常意味着您想要饱和到 1.0,而添加向量是线性发生的),但是在它们之上覆盖一个 float[] 就像这非常好,并且是与 GL/DirectX/等交换数据的公认方法。

不过,我建议您避免在同一函数范围内通过不同别名来引用同一成员,因为这会将您带入一个称为 load-hit-store 的令人讨厌的硬件停顿。如果可以的话,尤其要避免这种情况:

vector foo; 
foo.x = 1.0f;
return foo[0] + foo[1];
于 2009-01-30T06:05:20.733 回答
3

以下结构将具有请求的行为:

struct vertex
{
private:
    float data[3];
public:
    float &x, &y, &z;
    float &r, &g, &b;

    vertex() : x(data[0]), y(data[1]), z(data[2]), r(data[0]), g(data[1]), b(data[2]) {
    }

    float& operator [](int i) { 
        return data[i];
    }
};
于 2015-03-24T16:38:04.647 回答
2

我想你可以做一些宏观魔术来得到你想要的。但这看起来很难看。为什么要对 3 种不同类型使用相同的结构和顶点?为什么不能为颜色定义类?还要记住,顶点和颜色是不一样的。如果您将某些内容更改为顶点,那也会影响颜色,如果您对两者都有相同的类。

于 2009-01-30T05:59:39.077 回答
2

我不确定我是否正确理解了这个问题。但看起来您需要重载 operator[] 以提供类似数组的访问您的结构/类。请参阅此处提到的示例:运算符重载

于 2009-01-30T06:11:22.127 回答
1

我认为这是个坏主意,至少对于给出的示例:缺点是,对于任何解决方案,您可能都可以自由地将“rgb”实例分配给/来自“xyz”实例,这可能很少是明智或正确的。即你冒着放弃一些有用的类型安全的风险。

就个人而言,对于您给出的示例,我将 rgb 和 xyz 类型从基类boost::array<float,3>或类似类型中子类化。因此它们都继承了 operator[],可以传递给期望数组的函数,并以更安全的类型传递给期望颜色/坐标的事物。您通常希望将 xyz 或 rgb 视为数组,但很少将 xyz 视为 rgb,反之亦然。(rgb IS-A 数组:OK。xyz IS-A 数组:OK。rgb IS-A xyz ????我不这么认为!)

当然,这意味着对 x,y,z 和 r,g,b 的访问需要通过访问者(转发给适当的operator[](...))而不是直接访问成员。(为此,您需要 C# 的属性)。

于 2009-01-30T13:49:50.250 回答
0

我在下面有一个模板和两个 Vector 类,一个疯狂,一个理智。该模板实现了一个简单的在编译时固定的值数组。它专为子类化而设计,并使用受保护的数组变量来避免您必须跳过箍来访问数组。(有些人可能不喜欢这样的设计。我说,如果你的子类调用你的重载运算符,耦合可能是个好主意。)

这个疯狂的类允许你拥有名为 x、y、z 的成员变量,它就像一个用于调用 glGetFloatV 的数组。理智的只有访问函数 x()、y()、z() 并且仍然可以与 glGetFloatV 一起使用。您可以使用任一类作为您可能传递给 OpenGL 库的其他矢量对象的基础。尽管下面的类是特定于点的,但显然您可以进行搜索/替换以将它们变成 rgb 颜色类。

疯狂的类是疯狂的,因为语法糖 vec.x 而不是 vec.x() 的成本是 3 个参考变量。在大型应用程序中,这可能会占用大量空间。使用更简单的健全版本。

template <typename T, int N>
class FixedVector {
protected:
    T arr[N];
public:
    FixedVector();

    FixedVector(const T* a) {
        for (int i = 0; i < N; ++i) {
            arr[i] = a[i];
        }
    }

    FixedVector(const T& other) {
        for (int i = 0; i < N; ++i) {
            arr[i] = other.arr[i];
        }
    }

    FixedVector& operator=(const T& other) {
        for (int i = 0; i < N; ++i) {
            arr[i] = other.arr[i];
        }
        return *this;
    }

    T* operator&() { return arr; }
    const T* operator&() const { return arr; }

    T& operator[](int ofs) { 
        assert(ofs >= 0 && ofs < N);
        return arr[ofs];
    }
    const T& operator[](int ofs) const { 
        assert(ofs >= 0 && ofs < N);
        return arr[ofs];
    }
};

class CrazyPoint :  public FixedVector<float, 3> {
public:
    float &x, &y, &z;

    CrazyPoint()
      : x(arr[0]), y(arr[1]), z(arr[2])
    { arr[0] = arr[1] = arr[2] = 0.0; }

    CrazyPoint(const float* a)
      : x(arr[0]), y(arr[1]), z(arr[2])
    {
        arr[0] = a[0];
        arr[1] = a[1];
        arr[2] = a[2];
    }

    CrazyPoint(float a, float b, float c) 
      : x(a), y(b), z(c)
    {
        arr[0] = a;
        arr[1] = b;
        arr[2] = c;
    }
};

class SanePoint : public FixedVector<float, 3> {
public:
    float& x() { return arr[0]; }
    float& y() { return arr[1]; }
    float& z() { return arr[2]; }

    SanePoint() { arr[0] = arr[1] = arr[2] = 0.0; }
    SanePoint(float a, float b, float c) 
    {
        arr[0] = a;
        arr[1] = b;
        arr[2] = c;
    }
};

// usage
SanePoint normal;
glGetFloatV(GL_CURRENT_NORMAL, &normal);
于 2009-01-30T10:20:39.113 回答
0

您可以尝试添加对变量的引用,如下所示:

struct test {
        float x, y, z;
        float &r, &g, &b;

        test() : r(x), g(y), b(z) {}
    };

但是你的结构变得更大(从 12 字节到 40 字节)。

要在其上使用 [],请使用 operator[] 的重载,如前所述。

于 2009-01-30T13:07:19.103 回答
0

只是关于使用指向值成员的引用成员的警告。如果您曾经复制过这样的对象(例如按值传输),则需要定义一个复制构造函数(可能还有赋值运算符)。默认的复制构造函数将为您留下一个副本,其引用成员指向原始对象的值成员,而不是新对象的值成员。这当然不是你想要的。

正如已经指出的那样,考虑到您最终还会得到更大的对象,我认为使用访问器方法优于引用成员。

于 2009-03-08T16:54:38.270 回答
0

我认为张贴者正在寻找一些非常简单的东西,没有性能开销 - 正如你想要的 3D 矢量类之类的东西。因此,添加虚函数(vtable 间接成本)、额外成员(内存成本)、联合(每个新名称可能需要重新编译所有用户),甚至预处理器魔法(增加程序大小、跨类型等效性)都是不可取的。

现实世界的用例是采用模板化的 Vector3 类(可以是基于双精度或基于浮点的)并以用户友好的方式将其应用于其他场景。它可能是用 [x, y, z] 成员定义的,但如果您想将其用于旋转,您可能需要 [psi, theta, phi],用于速度 [dx, dy, dz] 等。

对于整个类型,您可以在编译时使用以下命令对其进行别名: using Rotation3 = Vector3;

但是似乎没有什么比为基础变量别名更简单或高效的方法了,对吧?

于 2022-02-18T20:26:46.953 回答