c++ - 如何使用既设置计数又填充数组的函数?
问题描述
Vulkan API 函数vkEnumerateInstanceExtensionProperties包含计数地址和数组地址作为参数。对于这个问题,可以忽略第一个参数。下面的函数签名:
VkResult vkEnumerateInstanceExtensionProperties(
const char* pLayerName,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties);
vkEnumerateInstanceExtensionProperties
文档定义的行为(上面链接):
如果pProperties
参数是nullptr
,则pPropertyCount
参数设置为可用结构的数量。
否则,pPropertyCount
应该反映pProperties
传递的数组的大小并填充数组。如果数组太小,pPropertyCount
则返回第一项(并且函数返回错误代码)。
我想这是 C++(或至少在 Vulkan API 中)中的一种常见设计选择,否则我在使用 Vulkan 的前几个小时不会偶然发现它,所以我的问题(我在下面问)可能得到更普遍的回答,但也欢迎 Vulkan 特定的答案。
我想检索此函数提供的所有结构。
我(松散地)遵循的Vulkan 教程指定我应该使用以下“算法”来实现它:
- 通过第一次调用检索元素计数
- 分配一些数组或数据结构
- 使用第二次调用填充数组
在看起来像这样的代码中(本教程使用向量并传递它的保留数组):
uint32_t count = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); // only retrieve count
VkExtensionProperties* list = new VkExtensionProperties[count];
vkEnumerateInstanceExtensionProperties(nullptr, &count, list); // now populate the array
// after use
delete[] list;
但是,这需要调用 Vulkan API 函数两次。我觉得这不是 Vulkan API 设计者的意图。是否有更优化的方法来检索结构列表?
Vulkan 文档页面指出,如果 API 状态发生变化,则连续调用之间可用结构的数量可能会有所不同。
解决方案
我想这是 C++ 中一个比较常见的设计选择
我想说这是C中常见的 [API] 设计选择。C++ 有其他/更好的方法来做到这一点(也许std::vector
在这里返回 a 是合适的)。
但是,这需要调用 Vulkan API 函数两次。我觉得这不是 Vulkan API 设计者的意图。
在我看来,这正是意图,否则教程不会这么说!否则你会怎么做?反正我不会太担心。如果这样做很昂贵,他们就不会那样做。
Vulkan 文档页面指出,如果 API 状态发生变化,则连续调用之间可用结构的数量可能会有所不同。
这听起来不太可能在实践中发生,仅仅看一眼那个链接,但如果你VK_INCOMPLETE
回来,你可以把结果扔掉并重新开始。
在看起来像这样的代码中......
这看起来不错 - 在 C 中 - 但在 C++ 中,本教程正在做什么(使用向量并传递其保留数组)将是我的选择。我想 Vulkan API 是故意用 C 编写的,以便 C 和 C++ 程序都可以使用它。
编辑,以解决 OP 在评论中关于为什么 Vulkan 选择这种 API 风格的问题:
嗯,很实用。抛开我发现的关于它应该是一个函数还是两个函数的相当不相关的细节(谁在乎,真的吗?),更好的问题是谁应该分配必要的内存来保存结果以及为什么。
有两种基本方法:
- 调用者分配所需的内存,然后在完成后负责释放它。
- 该库分配所需的内存并提供一个额外的(可能是通用的)函数来在调用者完成它时释放它。
方法1在Win32 API中被广泛使用,它的优点是您在教程中引用的示例,即this(为简洁起见省略了错误检查):
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
是可能的。如果库分配内存,这就更尴尬了。
方法 2 意味着您只需要调用vkEnumerateInstanceExtensionProperties()
一次,因为它可以分配所需的任何内存量并将其返回给您。
所以,方法 1 更灵活,方法 2 肯定更方便,也许更高效(效率多少完全取决于 API 背后的内容。
请注意:让库分配内存并让调用者释放它不是明智的选择。这样做是通向毁灭的道路(例如,他们很可能使用不同的堆,想象一下会造成的混乱)。
建议:将您计划使用的 Vulkan 部分封装在一个漂亮、友好的 C++ API 后面,该 API 在最合适的容器中返回这样的结果。如果你打算认真使用它,你会很高兴你做到了。
更新:哦,已经有人这样做了,请参阅上面的@Ekzuzy 评论。好的。