Вопрос или проблема
Я использую API, который ожидает непрерывный блок памяти, содержащий указатели. Указатели могут быть nullptr
.
В данный момент я использую массивы C:
ID3D11ShaderResourceView* srvs[] = {
room_diffuse_srv.Get(),
lightmap_srv.Get(),
room_normal_srv.Get(),
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
input.ambient_occlusion,
reflection_srv.Get(),
reflection_extents_srv.Get(),
nullptr,
nullptr,
nullptr
};
ctx->VSSetShaderResources(0, UINT(std::size(srvs)), std::data(srvs));
Мой инструментальный комплекс содержит clang-tidy, который советует избегать массивов C и предпочитать std::array
.
std::array
имеет руководящую схему вывода, позволяющую инициализировать такой массив следующим образом:
std::array srvs = {
room_diffuse_srv.Get(),
lightmap_srv.Get(),
room_normal_srv.Get(),
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
input.ambient_occlusion,
reflection_srv.Get(),
reflection_extents_srv.Get(),
nullptr,
nullptr,
nullptr
};
Это работает только в том случае, если первый элемент и все последующие элементы могут быть неявно преобразованы к одному и тому же (указательному) типу, в этом случае ID3D11ShaderResourceView*
.
Это нарушается, как только первый элемент содержит nullptr:
std::array srvs = {
nullptr,
lightmap_srv.Get(), // ошибка: "значение типа "ID3D11SamplerState *" не может быть использовано для инициализации сущности типа "std::_Enforce_same<std::nullptr_t, std::nullptr_t, ID3D11SamplerState *, std::nullptr_t, std::nullptr_t, ID3D11SamplerState *>::type" (также известный как "std::nullptr_t")"
room_normal_srv.Get(),
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
input.ambient_occlusion,
reflection_srv.Get(),
reflection_extents_srv.Get(),
nullptr,
nullptr,
nullptr
};
Это имеет смысл, потому что первый элемент имеет тип std::nullptr_t
.
Могу ли я инициализировать/вывести std::array
без явного указания типа и позволить первому элементу быть nullptr
? Я мог бы привести первый (nullptr
) элемент, но тогда я мог бы просто указать его в типе также.
Ответ или решение
Инициализация std::array
с использованием nullptr
без указания параметров шаблона является распространенной задачей при работе с API, требующими непрерывный блок памяти, содержащий указатели. Многие разработчики сталкиваются с этой проблемой, когда им необходимо передать массив указателей, среди которых могут встречаться и nullptr
. В данной статье мы подробно рассмотрим, как правильно инициализировать std::array
с учетом всех особенностей, включая использование nullptr
.
Введение в std::array
std::array
— это контейнер из стандартной библиотеки C++, который обеспечивает статический массив фиксированного размера. Одним из главных преимуществ использования std::array
вместо C-массивов является более безопасная и удобная семантика, включающая встроенные функции для получения размера массива и доступ к элементам.
Проблема с стандартной инициализацией
Как вы уже отметили, инициализация std::array
с первым элементом, равным nullptr
, может привести к проблемам с выводом типа. Это связано с тем, что при инициализации компилятор пытается вывести тип, основываясь на первом элементе. В случае, когда первый элемент — это nullptr
, компилятор интерпретирует его как std::nullptr_t
, что вызывает затруднения при дальнейшем добавлении элементов другого типа, например, ID3D11ShaderResourceView*
.
Решение проблемы
Одним из универсальных способов решения данной проблемы является указание типа при создании экземпляра std::array
. Вы можете сделать это следующим образом:
#include <array>
#include <cstddef>
#include <d3d11.h>
// Предполагая, что Get() возвращает указатели на нужные ресурсы
ID3D11ShaderResourceView* room_diffuse_srv;
ID3D11ShaderResourceView* lightmap_srv;
ID3D11ShaderResourceView* room_normal_srv;
std::array<ID3D11ShaderResourceView*, 15> srvs = {
nullptr,
lightmap_srv,
room_normal_srv,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
input.ambient_occlusion,
reflection_srv.Get(),
reflection_extents_srv.Get(),
nullptr,
nullptr,
nullptr
};
// Передача массива в контекст
ctx->VSSetShaderResources(0, UINT(srvs.size()), srvs.data());
Дополнительные советы
-
Использование std::nullptr_t: Если вы хотите сохранить гибкость и избежать явного указания на тип, можно использовать статическую инициализацию, но при этом нужно быть готовым к подключениям и конверсиям.
-
Проверка указателей: Всегда полезно проверять, не является ли указатель
nullptr
перед его использованием в функции или методе, чтобы избежать исключений и непредвиденных ошибок. -
Совместимость с API: Убедитесь, что ваш массив соответствует требованиям API относительно размера и типа указателей, чтобы избежать проблем с выводом типов.
Заключение
Инициализация std::array
с nullptr
без указания типов шаблона может показаться сложной задачей, однако, придерживаясь вышеописанных рекомендаций, вы сможете успешно создать массив указателей, включая nullptr
, без необходимости изменения логики вашего кода. Это позволит вам улучшить читаемость и безопасность вашего кода, следуя современным стандартам C++.