如何在 C++ 中仅将成员变量转换为字节数组?

How to turn only member variables into byte array in C++?

提问人:Tare 提问时间:9/6/2023 最后编辑:DailyLearnerTare 更新时间:9/6/2023 访问量:314

问:

TL;博士

我有一个结构体,不仅包含成员变量(例如,它们包含函数),而且我只想将成员变量转换为字节数组(/vector),这样我就可以在 Vulkan 中将数据上传到显卡。如何仅获取结构中表示成员变量的那部分?

我的具体方法

我有一个设置,我使用一个空结构并从中继承,...实际包含成员变量的结构。我使用它,这样我就可以在没有实际了解具体实现的情况下将成员留在一个班级中。ParamsBaseParamsAParamsBParamsBaseContainer

我想将类中的 Params 实例转换为字节缓冲区。Container

由于我需要实际//...struct,我在创建实例时为这些使用了通用子类,该子类允许我使用单个 getSize() 函数,而不是在每个子结构中覆盖它ParamsAParamsB

// =============================
// === Define Params structs ===
// =============================
struct ParamsBase
{
    virtual size_t getSize() const noexcept = 0;
};

struct ParamsA : public ParamsBase
{
    float vec[3] = { 2.3f, 3.4f, 4.5f };
};

// ===============================================================
// === enable sizeof query for derived structs from ParamsBase ===
// ===============================================================
template<class T>
struct GetSizeImpl : T
{
    using T::T;
    size_t getSize() const noexcept final { return sizeof(T); }
};

template<class T, class... Args>
std::unique_ptr<T> create(Args&&... args)
{
    return std::unique_ptr<T>(new GetSizeImpl<T>(std::forward<Args>(args)...));
}

// ============
// === Util ===
// ============
template<typename T>
std::vector<uint8_t> asByteVector(T* t, size_t size)
{
    std::vector<uint8_t> byteVec;
    byteVec.reserve(size);
    uint8_t* dataArr = std::bit_cast<uint8_t*>(t);
    byteVec.insert(byteVec.end(), &dataArr[0], &dataArr[size]);
    return byteVec;
}

// ============================================
// === Use Params struct in container class ===
// ============================================
class Container
{
public:
    Container(ParamsBase* params) : Params(params) {}

    const std::vector<uint8_t>& getParamsAsBuffer()
    {
        ParamsAsBuffer = asByteVector(Params, Params->getSize());
        return ParamsAsBuffer;
    }

    size_t getParamsSize() const { return Params->getSize(); }

private:
    ParamsBase* Params = nullptr;

    std::vector<uint8_t> ParamsAsBuffer;
};

使用所有这些,我的 Params 结构的大小太大,并且以包含一些垃圾 (?) 数据的两个字节开头。我认为它与函数有关,因为即使是带有函数的非模板化结构也存在此问题,但我不能确定这个假设是否正确。getSize()

这个小小的比较显示了我得到的和我想要的之间的差异:

// ================================
// === Define comparison struct ===
// ================================
struct ParamsCompareA
{
    float vec[3] = { 2.3f, 3.4f, 4.5f };
};

int main()
{
    // create instances
    auto pA = create<ParamsA>();
    Container cA(pA.get());
    std::vector<uint8_t> vecA = cA.getParamsAsBuffer();

    // comparison
    ParamsCompareA pcA;

    size_t sizeCompA = sizeof(ParamsCompareA);
    std::vector<uint8_t> compVecA = asByteVector(&pcA, sizeof(ParamsCompareA));

    std::cout << "ParamsA size: " << std::to_string(pA->getSize()) 
        << "; CompParamsA size: " << sizeof(ParamsCompareA) << std::endl;

    float* bufAf = reinterpret_cast<float*>(vecA.data());
    float* bufCompAf = reinterpret_cast<float*>(compVecA.data());
}

包含 12 个条目(即结构体有 12 个字节大),将它们重新解释为浮点数会显示正确的值。据报道有 24 个条目,我想要的实际数据是(从 0 开始)字节 2 到 14。compVecAvecA

我可以对 2 的偏移量进行硬编码(与 相同),但我很确定这不是处理此问题的正确方法。那么,有没有办法只访问我想要的数据部分呢?sizeof(GetSizeImpl)

Param 子结构的目的是让用户尽可能轻松地添加自己的参数结构并将其上传到 Vulkan 缓冲区 (/Shader),即他们应该只关心他们实际需要的数据,我可以在其他地方完成所有处理和转换。

C++ 结构 体大小

评论

5赞 Some programmer dude 9/6/2023
多态性和虚函数通常使用隐藏的私有成员变量来实现,该变量的大小包含在对象中。如果要使用多态性和虚函数,则不能只是将对象按原始字节复制到字节数组中。您需要序列化数据。sizeof
3赞 pptaszni 9/6/2023
我只读TL;DR 部分。如果你需要你的结构,那么只需让它成为 POD 并定义操作方法在其他地方:在另一个结构中通过组合,或作为自由函数。std::memcpy
2赞 Aconcagua 9/6/2023
顺便说一句:结构只包含数据,函数代码始终是独立的。但是,如果涉及虚拟函数,则有一个额外的隐藏 v-table 指针(或更复杂的继承结构上的几个指针)。
0赞 Tare 9/6/2023
@pptaszni谢谢,但我需要在不知道其确切类型的情况下获取结构的大小和字节数组副本(否则我将不得不为我的问题考虑一个完全不同的系统)。
0赞 pptaszni 9/6/2023
Oki 我明白你可能需要什么,但你的方法既过于复杂又错误——因为它包含 vtable..只需实现虚拟功能,您就可以开始了。它可以是微不足道的成员,也可以是更复杂的东西truct GetSizeImpl : Tstd::vector<uint8_t> Params::serialize();memcpy

答:

3赞 Mestkon 9/6/2023 #1

通过添加一些约定和布局,这可以与您当前的设置一起完成。

第一个修改是在结构内声明结构内的成员变量。Param

struct ParamsA : public ParamsBase
{
    struct Members {
        float vec[3] = { 2.3f, 3.4f, 4.5f };
    } m;
};

然后添加另一个虚拟函数ParamBase

virtual const void* data() const = 0;

现在我们可以改为 return,也可以覆盖其他函数GetSizeImpl::getSizesizeof(T::Members)

const void* data() const override { return &this->T::m; }

现在,您既有正确的数据字节大小,又有指向 POD 数据的指针,在法律上可以随心所欲。memcpy

评论

0赞 Tare 9/7/2023
这效果很好。我选择了第二个答案,因为对于创建自己的参数的人来说,这比开销要小一些,但这是一个很好的方法,谢谢。
2赞 Caleth 9/6/2023 #2

不要从任何东西派生。有一个包装器类型,它提供每个参数类型之间共享的行为。ParamsA

struct ParamsA // No inheritance!
{
    float vec[3] = { 2.3f, 3.4f, 4.5f };
};

struct BaseParameters {
    virtual std::span<std::byte> as_bytes() = 0;
};

template <typename T>
struct Parameters : BaseParameters {
    T data;
    /// ... other data members as you need
    std::span<std::byte> as_bytes() {
        return std::span(&data, 1).as_bytes();
    }
};

struct ParamsCompareA
{
    float vec[3] = { 2.3f, 3.4f, 4.5f };
};

int main()
{
    // create instances
    auto * pA = new Parameters<ParamsA>{};
    auto vecA = pA->as_bytes();

    // comparison
    ParamsCompareA pcA;

    std::cout << "ParamsA size: " << std::to_string(vecA.size()) 
        << "; CompParamsA size: " << sizeof(ParamsCompareA) << std::endl;

    float* bufAf = reinterpret_cast<float *>(vecA.data());
    float* bufCompA = pcA.vec;
}

评论

1赞 Tare 9/7/2023
只是一些注释,因为存在一些语法错误。它需要并且像魅力一样工作,谢谢。struct BaseParameters { virtual std::span<const std::byte> as_bytes() = 0; };template <typename T> struct Parameters : BaseParameters { T data; /// ... other data members as you need std::span<const std::byte> as_bytes() { return std::as_bytes(std::span(&data, 1)); } };