如何使用既设置计数又填充数组的函数?

How to work with functions that set both a count and populate an array?

Vulkan API 函数 vkEnumerateInstanceExtensionProperties 包含计数地址和数组地址作为参数。对于这道题,第一个参数可以忽略。下面的函数签名:

VkResult vkEnumerateInstanceExtensionProperties(
    const char*                                 pLayerName,
    uint32_t*                                   pPropertyCount,
    VkExtensionProperties*                      pProperties);

文档定义的 vkEnumerateInstanceExtensionProperties 的行为(上面链接):

如果 pProperties 参数为 nullptr,则 pPropertyCount 参数设置为可用结构的数量。

否则,pPropertyCount 应该反映传递的 pProperties 数组的大小,并且数组会被填充。如果数组太小,则返回前 pPropertyCount 项(函数 returns 错误代码)。

我想这在 C++(或至少在 Vulkan API)中是一个有点常见的设计选择,否则我不会在玩 Vulkan 的头几个小时里偶然发现它,所以我的问题(我在下面提出的问题)可能会得到更笼统的回答,但也欢迎提供特定于 Vulkan 的答案。


我想检索此函数提供的所有结构。

我(松散地)遵循的Vulkan tutorial指定我应该使用以下'algorithm'来实现它:

  1. 检索元素计数第一次调用
  2. 分配一些数组或数据结构
  3. 用第二次调用填充数组

在看起来像这样的代码中(本教程使用向量并传递它的保留数组):

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 documentation page 声明如果 API 状态发生变化,连续调用之间可用结构的数量可能会有所不同。

I imagine this is a somewhat common design choice in C++

我会说这是 C 中常见的 [API] 设计选择。 C++ 有其他/更好的方法来做到这一点(也许 returning a std::vector 在这里是合适的)。

However, this requires calling the Vulkan API function twice. It feels to me this was not the intention of the designers of the Vulkan API.

在我看来正是的意图,否则教程不会这么说!你还会怎么做?无论如何,我不会太担心。如果这样做很昂贵,他们就不会那样做。

The Vulkan documentation page states that the number of available structs may differ between successive calls if the API state changes.

这听起来不太可能在实践中发生,只是粗略地看了一眼 link,但如果你 VK_INCOMPLETE 回来了,你可以扔掉结果并重新开始。

In code that would look something like this...

在 C 中看起来不错 - 但在 C++ 中,教程正在做的事情(使用向量并传递其保留数组)将是我的选择。我想 Vulkan API 是故意用 C 编写的,以便 C 和 C++ 程序都可以使用它。


编辑,以解决 OP 在评论中提出的关于为什么 Vulkan 选择这种 API:

风格的问题

嗯,很实用。抛开我发现的关于它应该是一个函数还是两个函数的相当不相关的细节(谁在乎,真的?),更好的问题是谁应该分配必要的内存来保存结果以及为什么。

有两种基本方法:

  1. 调用者分配所需的内存,然后负责在用完后释放它。
  2. 该库分配所需的内存并提供一个额外的(可能是通用的)函数来在调用者使用完它时释放它。

方法 1 在 Win32 API 中被广泛使用,它的优点是您从教程中引用的示例,即这个(为简洁起见省略了错误检查):

uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

是可以的。如果是库分配内存,这个就比较尴尬了。

方法 2 意味着您只需要调用 vkEnumerateInstanceExtensionProperties() 一次,因为它可以分配它需要的任何内存量并 return 它给您。

所以,方法1更灵活,方法2当然更方便,也许效率更高(效率高多少完全取决于API背后的内容。

请注意:让库分配内存而调用者释放内存并不是一个明智的选择。这样做是走向毁灭的道路(他们可能正在使用不同的堆,例如,想象一下 会造成的混乱)。

建议:将您计划使用的 Vulkan 部分封装在一个漂亮、友好的 C++ API 之后,return 的结果最像这样适当的容器。如果您打算认真使用它,您会很高兴。

更新:哦,已经有人这样做了,请参阅上面@Ekzuzy 的评论。不错