关于struct:C ++成员变量别名?

C++ member variable aliases?

我很确定这是可能的,因为我很确定我已经看到了。我觉得这很好,但我会欣然接受"这是一个糟糕的想法,因为uuuuuuuuuuuuuuuuuuuuuuuuu"。

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

1
2
3
4
struct vertex
{
    float x, y, z;
};

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

1
2
3
4
5
6
7
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作为一个引用参数发送给一个函数,该函数期望有一个浮动数组,它将在其中存储数据(例如许多OpenGL glGet函数),那么它会很好地工作。

你怎么认为?可能吗?可能但愚蠢?


我要做的是制作访问器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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_;
}


联盟中的无名称嵌套结构不是标准C++。然而,这应该是有效的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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是指向顶点结构的float成员的指针。[3]意味着它是其中3个的数组。在重载运算符[]中,此数组被索引,指向数据成员的指针被取消引用并返回值。

此外,不管打包问题是什么,这个方法都可以工作——编译器可以随意填充顶点结构,不管它喜欢什么,它仍然可以正常工作。如果浮点数的包装不同,匿名联合将遇到问题。


使用工会?

1
2
3
4
5
6
union vertex
{
    struct { float x, y, z; };
    struct { float r, g, b; };
    float arr[3];
};

我不推荐-这会导致混乱。

补充:

正如阿德里安在他的回答中所指出的,这个与匿名结构成员的联盟不被ISO C++支持。它在GNU G++中工作(当您打开"EDOCX1"(0))时会抱怨不受支持。这让人想起了标准前C天(k&r 1st edn之前),当时结构元素名称在所有结构中都必须是唯一的,您可以使用约定的符号来获得结构内的偏移量,也可以使用其他结构类型的成员名称-无政府状态的一种形式。当我开始使用C时(很久以前,但在K&R1之后),这已经是历史性的用法了。

C11(ISO/IEC 9899:2011)支持匿名工会成员(两个结构)所示的符号,但C标准的早期版本不支持。ISO/IEC 1488∶2011(C++ 11)的第9.5节提供了匿名联合,但是GNU-EDCOX1〔1〕(4.9·1)不接受用EDCOX1×2表示的代码,标识"EDCOX1×3"。

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

提出了一个问题:

The three (x-y-z, r-g-b and the array) do not necessarily align.

它是三个元素的联合,三个元素从同一个地址开始。前两个是包含3个浮点值的结构。没有继承,也没有虚拟函数来提供不同的布局等。结构将与连续的三个元素一起布置(实际上,即使标准允许填充)。数组也从相同的地址开始,并且在结构中遵循"无填充"原则,元素与两个结构重叠。我真的不认为会有问题。


参考文献?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
};


你可以像其他人提到的那样,通过工会来实现这一点。将颜色和位置重载到同一个结构上可能不是一个好主意(例如,添加两种颜色通常意味着要饱和到1.0,而添加向量是线性的),但是在它们上面覆盖一个float[]是非常好的,也是一种公认的将数据与gl/directx/等交换的方法。

不过,我建议您避免在同一个函数范围内用不同的别名引用同一个成员,因为这会使您陷入一个称为加载命中存储的讨厌的硬件暂停。尤其是,如果您能够:

1
2
3
vector foo;
foo.x = 1.0f;
return foo[0] + foo[1];


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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];
    }
};


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


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


在我看来,这是个坏主意,至少在给出的例子中是这样的:缺点是,对于任何解决方案来说,您可能都能够自由地将"rgb"实例分配给/从"xyz"实例分配出去,这可能很少是明智或正确的。你冒着放弃一些有用的类型安全的风险。

就个人而言,例如您给出的示例,我将从基本boost::array或类似文件中对rgb和xyz类型进行子类划分。因此,它们都继承了运算符[],可以传递给期望数组的函数,并以更安全的类型传递给期望颜色/坐标的对象。通常您希望将XYZ或RGB视为数组,但很少希望将XYZ视为RGB,反之亦然。(rgb is-a array:好。XYZ是一个数组:好的。RGB是一种XYZ?????我不这么认为!)

当然,这意味着访问X、Y、Z&R、G、B需要通过访问器(转发到适当的operator[](...))而不是直接访问成员。(你需要C的属性)。


只是关于使用指向值成员的引用成员的警告。如果复制了这样的对象(如按值传递),则需要定义一个复制构造函数(也可能是赋值运算符)。默认的复制构造函数将为您保留一个副本,该副本的引用成员指向原始对象的值成员,而不是新对象的值成员。这肯定不是你想要的。

正如前面指出的那样,考虑到您最终也会得到更大的对象,我认为使用访问器方法比引用成员更可取。


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

1
2
3
4
5
6
struct test {
        float x, y, z;
        float &r, &g, &b;

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

但是您的结构会变大(从12字节到40字节)。

要对其使用[],请使用前面提到的运算符[]的重载。


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

Crazy类允许您使用名为x、y、z的成员变量,它的作用类似于调用glgetfloatv的数组。SANE只具有访问函数x()、y()、z(),并且仍然可以使用glgetfloatv。您可以使用任何一个类作为可能传递给OpenGL库的其他向量对象的基础。尽管下面的类是特定于点的,但显然您可以只进行搜索/替换,将它们转换为RGB颜色类。

疯狂类是疯狂的,因为用sugar vec.x代替vec.x()的代价是3个引用变量。在大型应用程序中,这可能会占用大量空间。使用更简单、理智的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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);