TL;博士
我有一个不仅仅是成员变量的结构体(例如它们包含函数),并且我只想将成员变量转换为字节数组(/向量),这样我就可以将数据上传到Vulkan中的显卡。如何仅获取结构体中代表成员变量的部分?
我的具体做法
我有一个设置,我使用一个空的
ParamsBase
结构,并从中继承 ParamsA
,ParamsB
...实际上包含成员变量的结构。我使用它,这样我就可以将 ParamsBase
的成员保留在 Container
类中,而无需实际了解具体实现。
我想将
Container
类中的Params实例变成字节缓冲区。
由于我需要实际
ParamsA
/ParamsB
/... 结构的大小,因此在创建实例时我使用了一个通用子类,该子类允许我使用单个 getSize()
函数,而不是覆盖它在每个子结构中。
// =============================
// === 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());
}
compVecA
包含 12 个条目(即结构体有 12 个字节大),将它们重新解释为浮点数会显示正确的值。据报告,vecA
有 24 个条目,我想要的实际数据位于(基于 0)字节 2 到 14。
我可以硬编码偏移量 2(与
sizeof(GetSizeImpl)
相同),但我很确定这不是处理此问题的正确方法。那么,有没有办法可以只获取我想要的数据部分?
Param 子结构的目的是让用户尽可能轻松地添加自己的参数结构并将其上传到 Vulkan 缓冲区(/Shader),即他们应该只关心他们实际需要的数据,而我可以在其他地方完成所有处理和转换。
通过添加一些约定和布局,这可以与您当前的设置一起完成。
第一个修改是在
Param
结构体中声明结构体内部的成员变量。
struct ParamsA : public ParamsBase
{
struct Members {
float vec[3] = { 2.3f, 3.4f, 4.5f };
} m;
};
然后添加另一个虚函数到
ParamBase
virtual const void* data() const = 0;
现在我们可以将
GetSizeImpl::getSize
更改为返回 sizeof(T::Members)
,并覆盖其他函数
const void* data() const override { return &this->T::m; }
现在您拥有了正确的数据字节大小和指向 POD 数据的指针,您可以合法地
memcpy
随意使用。
不要从任何事物中衍生
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;
}