Я хотел бы создавать простые векторы шейдеров, конвейеров, текстур и других объектов, используемых с API Vulkan, но мне трудно понять, как использовать конструкторы копирования, конструкторы перемещения, операции копирования-назначения и перемещения-назначения.
Моя программа создает вектор шейдерных структур для передачи в конвейер для создания:
std::vector<vk::Shader> testShaders{
{"vertex.vert", VK_SHADER_STAGE_VERTEX_BIT},
{"vertex.frag", VK_SHADER_STAGE_FRAGMENT_BIT}
};
vk::GraphicsPPL<triangleList> testPPL(testShaders);
Вышеупомянутый метод возвращает ошибку уровня проверки, где находятся модули шейдера VK_NULL_HANDLE. В другом вопросе о переполнении стека решение загрузки векторов заключалось в реализации правила трех, но сейчас я пытаюсь понять, как это сделать, не вызывая других ошибок. Я предпринял несколько попыток создать оператор копирования-присваивания и конструктор копирования, используя cpp-справочную страницу, но, похоже, мне не удалось избежать сбоя моей программы. Вот некоторые из моих попыток:
Shader(const Shader& other) // First attempt: copy constructor
{
std::memcpy(this, other.shaderModule, sizeof(other.shaderModule));
}// exited with code -1073741819.
Shader(const Shader& other) // Second attempt
{
vkDestroyShaderModule(GPU::device, shaderModule, nullptr);
//delete[] shaderModule; //With or without, does not change the outcome
std::memcpy(shaderModule, other.shaderModule, sizeof(other.shaderModule));
} // Couldn't find VkShaderModule Object 0xcdcdcdcdcdcdcdcd and VK_NULL_HANDLE
/* Third attempt */
Shader(const Shader& other)
: shaderStage(other.shaderStage){}
Shader& operator=(const Shader& other) // III. copy assignment
{
if (this == &other)
return *this;
VkShaderModule new_shaderModule{};
//sizeof(new_shaderModule) returns 8
//sizeof(other.shaderModule) returns 8
std::memcpy(new_shaderModule, other.shaderModule, sizeof(other.shaderModule));
vkDestroyShaderModule(GPU::device, shaderModule, nullptr); // No difference with or without, neither replacing or adding "delete[] shaderModule"
shaderModule = new_shaderModule;
return *this;
}// VK_NULL_HANDLE error
Shader(const Shader& other) // Fourth attempt - forgot to add this one
{
std::memcpy(this, &other, sizeof(Shader));
}
Моя структура шейдера приведена ниже:
struct Shader {
VkShaderModule shaderModule;
VkShaderStageFlagBits shaderStage;
Shader(std::string const& filename, VkShaderStageFlagBits stage)
: shaderStage(stage)
{
auto code = readFile(".\\shaders\\" + filename + ".spv"); // Function omitted from post for brevity, please don't hesitate to ask if needed
createShaderModule(code, filename);
}
~Shader() {
vkDestroyShaderModule(GPU::device, shaderModule, nullptr);
// Delete[] shaderModule // With or without, did not change the outcome
}
void createShaderModule(const std::vector<char>& code, const std::string& filename) {
VkShaderModuleCreateInfo createInfo
{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
if (vkCreateShaderModule(GPU::device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("failed to create " + filename + "!");
}
}
/* My failed attempts here */
}
На всякий случай вот как Vulkan определяет функцию vkCreateShaderModule:
// Provided by VK_VERSION_1_0
VkResult vkCreateShaderModule(
VkDevice device,
const VkShaderModuleCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkShaderModule* pShaderModule);
Редактировать:
Дополнения к шейдеру struct на основе рекомендаций из комментариев:
Shader(const Shader& other) = delete;
Shader& operator=(const Shader& other) = delete;
Shader(Shader&& other) noexcept // move constructor
: shaderModule(std::exchange(other.shaderModule, nullptr)), shaderStage(std::exchange(other.shaderStage, nullptr)) {}
Shader& operator=(Shader&& other) noexcept // move assignment
{
std::swap(shaderModule, other.shaderModule);
return *this;
}
и несколько новых ошибок, ура...
Error C2440
'=': cannot convert from 'nullptr' to '_Ty'
/* path */ \MSVC\14.37.32822\include\utility
Error (active) E0304
no instance of function template "std::construct_at" matches the argument list
/* path */ MSVC\14.37.32822\include\xmemory
Редактировать 2:
Изменение определений хода:
Shader(const Shader& other) = delete;
Shader& operator=(const Shader& other) = delete;
Shader(Shader&& other) noexcept // move constructor
{
std::swap(*this, other);
// std::exchange(*this, other); // no dice either
}
Shader& operator=(Shader&& other) noexcept // move assignment
{
std::swap(*this, other);
return *this;
}
Новая ошибка:
Error C2280 'vk::Shader::Shader(const vk::Shader &)'
attempting to reference a deleted function
/* path */\MSVC\14.37.32822\include\xutility
Я попробовал решение shaderModule(nullptr) в дополнение к закомментированию двух строк delete без разницы в ошибке.
Может стоит сделать Shader только подвижным? То есть реализовать только конструктор перемещения и оператор присваивания перемещения? Имеет ли логический смысл копировать Shader?
Это приводит к вопросу: хотите ли вы скопировать ресурс или вам нужны оба Shader, чтобы «поделиться» ресурсом? Если последнее, обратите внимание на std::shared_ptr. В первом случае вам необходимо убедиться, что вам действительно нужна копия. Возможно, отключение копирования и реализация только операций перемещения больше соответствует вашим требованиям.
Я бы предпочел, чтобы ресурс был перемещен. Как мне отключить копии? Я пробовал реализовать конструкторы/операторы присваивания копирования и перемещения, как определено в разделе «Правило пяти» на сайте cpp-resources, но моя программа постоянно использовала операцию копирования.
@ModernEraCaveman Shader(const Shader&)= delete; отключает конструктор копирования.
Shader(const Shader& other) = delete; и Shader& operator=(const Shader& other) = delete;Примечание. Если цель состоит в том, чтобы поместить Shader в vector или другой контейнер стандартной библиотеки, вам нужно сделать перемещение специальными функциями-членами , не вызывающими . Контейнеры библиотеки не будут перемещать объект, если в процессе перемещения они могут вызвать исключение и оставить контейнер в неопределенном состоянии.
@user4581301 user4581301 как добавить noException в часть конструктора/функцию createShaderModule? Я реализовал операции перемещения и ваши предложения по удалению операций копирования, поэтому сейчас я обновлю свой пост, указав эти и некоторые новые ошибки.
Shader(Shader&& other) noexcept { /* move logic */ } опять то же самое с перемещением-назначением. Если этот ход заденет что-то, что обычно выбрасывает, программа вместо этого завершится. Звучит жестко, но это лучше, чем устранять скрытое повреждение данных.
Примечание: сейчас рассмотрим реализацию оператора присваивания посредством копирования и замены . Часто он неоптимален с точки зрения производительности, но он настолько прост, что ошибиться практически невозможно. Профилирование результата позволит вам узнать, нужны ли вам дополнительные возможности более сложного решения.
Можно ли присвоить VkShaderModulenullptr? Например, это указатель? Это задокументировано как непрозрачный дескриптор, поэтому вы на самом деле не знаете, что это такое, и это может быть просто целое число, индексирующее слот где-то в таблице. и это может быть что-то более сложное. Возможно, вам понадобится кто-то, кто действительно знает все тонкости Vulkan, чтобы помочь вам с этим. Это не то, что я использую в повседневной работе со встроенными системами.
Проверьте вышесказанное с помощью дурацкого теста, например, shaderModule = nullptr;, оставленного где-то в коде. Вы можете получить лучшую ошибку компилятора, указывающую прямо на эту строку, а не на что-то, скрытое в глубокой тьме стандартной библиотеки.
Я не уверен, что вы хорошо разбираетесь в правиле 3. Полное название — правило 0/3/5. И фокусом является деструктор; Я не вижу в вашем фрагменте деструктора. Примечание: использование std::memcpy почти всегда является плохой практикой, особенно если это один из аргументов.
@user4581301 только что обновил сообщение, и я также попробовал использовать предложенный вами тест, выполнив Shader(Shader&& other) noexcept : shaderModule(nullptr) без каких-либо различий в возвращаемой ошибке.
Если я прав насчет виновника, shaderModule(nullptr) не должен менять сообщение об ошибке, мы все равно назначаем nullptr чему-то, что его не примет, но номер строки должен. Если номер строки не меняется, то я ошибаюсь.
Не обращайте внимания на мое последнее редактирование. Я напутал что-то еще, что на самом деле не решило проблему.
Вот простой пример, который, вероятно, лишь немного неверен, потому что я не знаю, что можно использовать в качестве безопасного значения удержания «Нет такого VkShaderModule»: godbolt.org/z/j94Pqa7s9 Я использовал ноль в качестве значения «нет». VkShaderModule значение для этого примера. Это может выдержать. Возможно, нет.
Да, я думаю, это как-то связано с тем, как определяет Вулкан VkShaderModule. Я скопировал ваше решение и заменил заполнители правильными значениями, но все равно ничего. Я провел собственное тестирование и думаю, что это связано с тем, что VkShaderModule определяется с помощью макросов. Мне удалось сделать минимально воспроизводимую версию, не используя ни одной библиотеки Vulkan: страница github
Я все еще ищу, где в библиотеке определен VkShaderModule_T, поскольку это может пролить свет на решение.
В вашем примере TEST_HANDLE(foo); расширяется до typedef struct foo_T *foo;. Никто еще не заявил foo_T. и поскольку нет определения foo_T, вы не можете delete[] Foo; В вашем примере на самом деле ничего из этого делать не нужно. Но что вас действительно привлекает, так это инициализация списка. std::vector<rofTest> testi {{"file1.shader", test1}}; создает rofTest в linitializer_list и должен передать rofTests в ссылку vector по const. Которые невозможно сдвинуть, потому что они const. Боль в заднице. Вы не можете перемещать предметы из initializer_list.
Вот обходные пути: stackoverflow.com/q/8468774/4581301
Спасибо за ссылку на эту тему. Для моего собственного понимания синтаксиса конструктора и присваивания: что является источником, а что назначением? то есть object/*source*/(const object& destination) или object/*dst*/(const object& src)?
object/*dst*/(const object& src). Конструируемый объект будет копией объекта, переданного конструктору. Аналогично с операторами присваивания. Объект, для которого вызывается присваивание (слева), становится копией объекта с правой стороны.





Я не могу не думать, что вы усложняете себе задачу больше, чем нужно. «Я хотел бы создавать простые векторы шейдеров, конвейеров, текстур и других объектов, используемых с API Vulkan» — действительно ли std::vector необходим? Примечательно, что std::vector будет перемещать объекты в памяти при каждом изменении размера контейнера.
В случае создания объекта графического конвейера Vulkan вам понадобится вектор vkShaderModule, и его можно хранить внутри одного объекта «Шейдер». И как только конвейер будет создан, вы сможете уничтожить эти шейдерные модули. (если только не требуется воссоздание конвейера)
В моей собственной библиотеке Vulkan (к сожалению, я не могу открыть исходный код!) Я использую непрозрачные указатели для представления объектов шейдера, например:
// shaders.hpp
struct Shader; // opaque handle defined here
Shader* create_shader(std::string_view vert, std::string_view frag);
void destroy_shader(Shader* shader);
std::vector<vkShaderModule> get_shader_modules(Shader* shader);
// shaders.cpp
struct Shader {
// definition here
};
Shader* create_shader(std::string_view vert, std::string_view frag) {
auto shader = new Shader;
// ...
return shader;
}
void destroy_shader(Shader* shader) {
// ...
delete shader;
}
// main.cpp
auto shader = create_shader("vertex.vert", "vertex.frag");
if (shader == nullptr) { /* FAIL */ }
// use shader here...
destroy_shader(shader);
shader = nullptr;
Внедрение механизмов объектно-ориентированного программирования имеет тенденцию к чрезмерному усложнению вещей, как здесь, где вы боретесь с конструкторами перемещения и memcpy, главным образом потому, что выделение/освобождение памяти обычно размещается внутри конструкторов/деструкторов. В некоторых случаях это хорошая модель, но она может привести к появлению множества вопросов без ответов.
Примечательно, что если теперь вы хотите переместить дескриптор шейдера, передать его другим функциям и т. д., вы перемещаете только указатель. Таким образом, вы можете устранить дополнительную сложность, связанную с запоминанием передачи const ref, чтобы избежать копирования, или проверкой того, что конструкторы перемещения/копирования/операции присваивания действительно вызываются правильно.
Возможно, это субъективно, но это похоже на изобретение очень сложной проблемы для решения простой проблемы. (за исключением того, что в Вулкане нет ничего «легкого»). Надеюсь, это поможет.
Спасибо большое @alexpanter. Мне потребовалось слишком много времени, чтобы понять, что простые указатели будут лучшим решением и что std::vector не нужны. В итоге я реализовал решение, подобное вашему, используя массив/указатель для передачи шейдеров, где vk::Shader shaders[] = { /*list constructor 1*/, /* list constructor 2 */, ... }.
Примечание. Практически никогда не требуется назначать или
memcpythis. Есть подходящее время для этого, но это не один из них. Попытка 1 выполняет побитовое копирование, поэтому вы получите два объекта, которые будут думать, что они владеют одним и тем же ресурсом, поскольку был скопирован дескриптор ресурса, а не сам ресурс.