在 C++ 中使用联合时是否可以跳过字节?

Is it possible to skip bytes when using union, in c++?

我正在尝试对几个结构使用联合,特别是将 2D(4 x 4)浮点数组与 4 个 Vector4 对齐。

据我了解,union 从第一个开始查看顺序字节。如果我有一个 4x4 数组并且想要得到一整列,我需要跳过 16 个字节才能到达下一行。

示例:

Matrix = {{a, b, c, d},
          {e, f, g, h},
          {h, i, j, k},
          {l, m, n, o};

如果给定数据使得一维数组为 {a,b,c,d,e,..., o},我将如何获得最后一列 (d,h,k,o)?

那么如何获取字节:12->16、28->32、44->48、60->64?

我的代码 returns 最后一行(不是我想要的)是:

//matrix.h
struct Matrix4 {
    union{
        float values[4][4];
        Vector4 column[4];
    };

    //matrix methods etc
}

//main.cpp
Matrix4 position = Matrix3::translation(Vector3(1,2,3));
Vector4 column = position.column[2];
std::cout << (column) << std::endl;

输出矩阵将是(不记得如何使用 LaTex 格式化抱歉):

| 1 0 0 1 |
| 0 1 0 2 |
| 0 0 1 3 |
| 0 0 0 1 |

所以我想要的栏目是:

|1|
|2|
|3|
|1|

工会给出:

|0|
|0|
|0|
|1|

(最后一行,不是最后一列)。

重构我的代码以使用一维数组来获取顺序字节是否会更容易,还是我应该更改我的矩阵的构造方式?或者我可以结合其他一些事情来处理这个问题吗?

编辑:我的问题与此 other question 不同,因为我已经创建了一个矩阵 class 并分配了内存。我的问题着眼于 "union" 的功能及其限制,以及使用我定义的结构 'Vector4' 作为在矩阵中表示某些数据的手段。我正在研究跳过字节以接收不直接相互跟随的数据的可能性,而不是如何分配所述字节。

以下答案适用于 G++ 编译器,如果您想重复访问同一矩阵的列,它会更有效。

#include <iostream>

using namespace std;

typedef struct _Vector4f
{
    float entries[4];
} Vector4f;

typedef struct _Matrix4f
{
    union {
        float entries[16];
        Vector4f rows[4];
    };

    // cache the pointer to the transposed matrix
    struct _Matrix4f *
    _trans_cache;

    struct _Matrix4f *
    transpose();

} Matrix4f;

Matrix4f *
Matrix4f::transpose()
{
    if (this->_trans_cache)
        return this->_trans_cache;

    Matrix4f * result = new Matrix4f;

    for (int k = 0; k < 16; k++)
    {
        int j = k % 4;
        int i = k / 4;

        result->entries[k] = this->entries[i + 4 * j];
    }

    return this->_trans_cache = result;
}

int main()
{
    Matrix4f mat = {1.0f, 0.0f, 0.0f, 1.0f,
                    0.0f, 1.0f, 0.0f, 2.0f,
                    0.0f, 0.0f, 1.0f, 3.0f,
                    0.0f, 0.0f, 0.0f, 1.0f};
    Vector4f col = mat.transpose()->rows[3];

    cout << (col.entries[0]) << ','
            << (col.entries[1]) << ','
            << (col.entries[2]) << ','
            << (col.entries[3]) << end;

    return 0;
}

最后,我想让 OP 知道有很多库可以实现许多复杂的矩阵运算,例如 GNU Scientific Library。所以几乎没有任何理由再次发明轮子。呵呵。 :P

您不能同时访问工会的两个不同成员。来自 cppreference 的示例:

#include <string>
#include <vector>

union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
};          // the whole union occupies max(sizeof(string), sizeof(vector<int>))

int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is undefined behavior
[...]

即分配 str 成员后,您将无法访问 vec 成员。

因此,我决定添加一个全新的答案,我认为涉及 none 未定义的行为。主演力类型转换:

#include <cstdlib>
#include <iostream>
#include <initializer_list>

using namespace std;

// Matrix4f dimension
#define DIM_MAT4        (4)

// Data structures
typedef struct _Vector4f
{
    float entries[DIM_MAT4];

    void
    println() const;

} Vector4f;

typedef struct _Matrix4f
{
    _Matrix4f(initializer_list<float> fl) : _trans_cache(NULL), _renew_cache(true)
    {
        copy(fl.begin(), fl.end(), entries);
    }

    _Matrix4f() : entries{.0f, .0f, .0f, .0f,
                    .0f, .0f, .0f, .0f,
                    .0f, .0f, .0f, .0f,
                    .0f, .0f, .0f, .0f}, _trans_cache(NULL), _renew_cache(true) {}

    ~_Matrix4f() { delete _trans_cache; }

    float &
    operator() (int, int);

    const float &
    operator() (int, int) const;

    const Vector4f *
    getRow(int) const;

    const Vector4f *
    getCol(int) const;

    _Matrix4f *
    transpose() const;

    void
    println() const;

private:

    float entries[DIM_MAT4 * DIM_MAT4];

    // cache the pointer to the transposed matrix
    mutable _Matrix4f *
    _trans_cache;
    mutable bool
    _renew_cache;

} Matrix4f;

Matrix4f *
Matrix4f::transpose() const
{
    if (not _renew_cache)
        return this->_trans_cache;

    Matrix4f * result = new Matrix4f;

    for (int k = 0; k < DIM_MAT4 * DIM_MAT4; k++)
    {
        int j = k % DIM_MAT4;
        int i = k / DIM_MAT4;

        result->entries[k] = this->entries[i + DIM_MAT4 * j];
    }

    _renew_cache = false;
    return this->_trans_cache = result;
}

float &
Matrix4f::operator() (int rid, int cid)
{
    _renew_cache = true;
    return this->entries[rid * DIM_MAT4 + cid];
}

const float &
Matrix4f::operator() (int rid, int cid) const
{
    return this->entries[rid * DIM_MAT4 + cid];
}

const Vector4f *
Matrix4f::getRow(int rid) const
{
    return reinterpret_cast<Vector4f *>(&(this->entries[rid * DIM_MAT4]));
}

const Vector4f *
Matrix4f::getCol(int cid) const
{
    return this->transpose()->getRow(cid);
}

void
Matrix4f::println() const
{
    this->getRow(0)->println();
    this->getRow(1)->println();
    this->getRow(2)->println();
    this->getRow(3)->println();

    cout << "Transposed: " << this->_trans_cache << endl;
}

void
Vector4f::println() const
{
    cout << '(' << (this->entries[0]) << ','
            << (this->entries[1]) << ','
            << (this->entries[2]) << ','
            << (this->entries[3]) << ')' << endl;
}

// Unit test
int main()
{
    Matrix4f mat = {1.0f, 0.0f, 0.0f, 1.0f,
                    0.0f, 1.0f, 0.0f, 2.0f,
                    0.0f, 0.0f, 1.0f, 3.0f,
                    0.0f, 0.0f, 0.0f, 1.0f};

    mat.println();

    // The column of the current matrix
    // is the row of the transposed matrix
    Vector4f * col = mat.getCol(3);

    cout << "Vector:" << endl;

    col->println();

    cout << "Matrix(2,3) = " << mat(2, 3) << endl;

    return 0;
}

联合仅用于为不同的对象保留一块内存区域。但在每一瞬间只能有一个活着的物体。所以作为一般规则,如果你有一个包含两个成员 ab 的联合,如果你已经初始化了 a 并且 a 是联合的活跃成员,你应该永远不要尝试读取 b.

的值

但在 ab 共享一个公共初始序列的情况下,此规则有一个例外:在这种情况下,标准确保 aa 的内存表示b 足够相似,因此您可以通过 b.

访问 a 的值

下面的代码示例显示了通过非活动成员访问联合值的定义行为:

    struct Vector4 {
       float values[4];
       };
    //I propose you two kinds of matrix, as is usualy done in linear algebra
    //left_matrix are efficient for matrix multiplication if they are on the
    //left side of the * symbol.
    struct left_matrix{
       Vector4 rows[4];
       //for this simple example we index using call operator:
       float& operator()(int i,int j){
           return rows[i].values[j];
           }
       };

    struct right_matrix{
       Vector4 columns[4];
       float& operator()(int i, int j){
          return columns[j].values[i];//the actual memory transposition is here.
          }
        };

    right_matrix 
    transpose_left_matrix(const left_matrix& m){
        union{
          left_matrix lm;
          right_matrix rm;
          };
        lm = m; //lm is the active member
        return rm; //but we can access rm
        }

这是一个相当常见的模式,但无效。它很受欢迎,因为它很简单,而且主流编译器传统上允许并支持它。

但是您不能以这种方式以明确定义的方式使用联合。联合不是一种将一种类型的数据重新解释为另一种类型的数据的方法。哎呀,reinterpret_cast 甚至不能让你到达那里:要安全地做到这一点,你必须保持 std::copy 来回传输你的字节。唯一安全的情况是使用 char[](或 unsigned char[],或 std::byte[]),它允许为任意内存块设置别名:因此,联合可用于快速字节检查.除此之外,它们仅在您一次不需要使用多个联合成员时才有用。

你真正应该做的是选择一个layout/format,然后在上面添加你自己的operator[](或其他函数)变体以提供不同的在您的呼叫站点解释数据的界面和方法。

做对了,不会有任何开销。

巧妙地做到这一点,您将能够以您需要的方式应用您的转换 "on the fly"。提供一种代理类型,一种迭代器,它不仅以线性序列迭代,而且以您需要的修改后的序列迭代。

这个逻辑都可以封装在你的Matrix4类型中。