Хорошая практика в UE5 — убедиться, что вы не вызываете функцию с nullptr.
Одним из способов сделать это будет вызов функции указателей IsValidLowLevel() следующим образом:
if (ActorPtr->IsValidLowLevel())
{
ActorPtr->DoSomething();
}
Но почему этот указатель актера имеет гарантированный доступ к этой функции, если он действительно имеет значение nullptr? Не должна ли программа аварийно завершиться, если вы вызываете функцию с нулевым значением nullptr так же, как если бы я вызывал функцию DoSomething() ActorPtr? И есть ли разница в том, как эти функции называются?
Я также знаю, что есть другие и, возможно, лучшие способы проверки нуля, поскольку он также выполняет множество других проверок, которые могут быть не нужны, но мне просто интересно, почему вы вообще можете это сделать.
Я просмотрел документацию UE5 по этой функции, но она очень полезна и говорит, что она выполняет нулевую проверку, и не вдается в подробности, как она это делает.
Я бы сказал, что if (ActorPtr->IsValidLowLevel())
, наверное, должно быть if (ActorPtr && ActorPtr->IsValidLowLevel())
.
Хотя многие люди скажут вам, что вызов метода для нулевого объекта — это UB (и они правы), на практике компиляторы могут генерировать разумный, не вызывающий сбоев код, который ведет себя предсказуемо для формального UB. если ActorPtr
на самом деле указывает на действительную память, которую вы можете прочитать, но НЕ на UObject, тогда сгенерированный код обращается только к тривиальному члену с фиксированным смещением относительно объекта (индекс объекта), и крайне маловероятно, что объект в глобальном таблица по этому индексу является переданным объектом. Вот почему метод говорит: «Верните true, если это действительный объект».
... IsValidLowLevel обязательно произойдет сбой, если данный указатель не указывает на допустимый объект, и если он не указывает на читаемую память, или он смещен, или компилятор вставляет дополнительные проверки для вызова метода с нулевой указатель (или, возможно, по другим причинам, которые я не вижу). Но НЕ гарантировано, что произойдет сбой, и истинность проверки не является гарантией того, что указатель указывает на действительный объект.
Чтобы охватить некоторые из этих ответов! IsValidLowLevel Не происходит сбой, если указатель действительно равен нулю, поэтому этот вопрос вообще важен. и if (ActorPtr->IsValidLowLevel())
идентично выполнению if (ActorPtr && ActorPtr->IsValidLowLevel())
, достигают того же самого, поскольку IsValidLowLevel()
используется для проверки нуля
@Wardy «IsValidLowLevel не дает сбоя, если указатель действительно равен нулю» — возможно, в настоящее время он не аварийно завершает работу с вашим текущим флагом компилятора и сборки, но он недействителен в соответствии со стандартом C++ и может перестать работать в любой момент. Такова природа неопределенного поведения; любое поведение приемлемо, в том числе поведение, ожидаемое вами, но нет никаких гарантий относительно поведения, которое вы наблюдаете в настоящее время или какое поведение вы можете наблюдать в будущем. Так что просто не пишите код с помощью UB. Простого тестирования кода и наблюдения за его поведением недостаточно, чтобы заявить, что программа является допустимой C++.
См. Вызов метода с NULL-указателем, который не обращается к каким-либо данным, когда-либо терпит неудачу? для обсуждения этого вопроса.
Разыменование nullptr
— это неопределенное поведение. Сбой не гарантирован, что-либо (буквально) может произойти, и компилятор не обязан предупреждать вас об этом - он может просто предположить, что этого никогда не произойдет (потому что, если бы это произошло, это означало бы, что вы нарушаете правила, и тогда это разрешено делать что угодно). А если функция проверяет наличие nullptr
внутри, то это просто бессмысленно, поскольку this
никогда не может находиться nullptr
внутри функции-члена, поэтому любой разумный оптимизирующий компилятор просто оптимизирует любую такую проверку.
Вероятно, в вашей нынешней ситуации вы хотите if (ActorPtr && ActorPtr->IsValidLowLevel())
.
Этой логике я тоже следовал, но дело в том, что функция работает, если указатель действительно равен нулю. Я думаю, что один из комментаторов что-то заметил, говоря, что функция просматривает область памяти, которую занимает указатель, и проверяет, «кажется ли она uобъектом». Но ваша логика — это именно то, почему я был так озадачен тем, почему эта функция вообще работает, потому что она противоречит всему, чему меня учили в основах.
@wardy "но дело в том, что функция работает, если указатель действительно равен нулю" - UB позволяет происходить чему угодно, включая вызов функции и поведение так, как вы ожидаете - это включено в набор "чего угодно". Это не означает, что код не сломан и что поведение не может измениться в любой момент (смена компилятора, изменение уровня оптимизации компилятора, изменение версии компилятора и т. д. и т. п.). Код недействителен/сломан, и вы не можете полагаться на то поведение, которое наблюдаете в данный момент.
Еще раз спасибо за быстрый ответ! Если это так, то я не понимаю, почему в Unreal Engine должна быть встроенная функция, предназначенная для проверки нуля, если UB будет происходить всякий раз, когда указатель на самом деле равен нулю, а не является статической логической функцией, в которую можно поместить указатель. и проведите нулевую проверку таким образом
@wardy «Я не понимаю, почему в Unreal Engine есть встроенная функция, предназначенная для проверки нулевых значений» - я тоже не знаю, но могу придумать несколько потенциальных причин; Некомпетентность. Лень. Невежество. «Кажется, это работает для меня с моим подходом к цепочке инструментов (который во многом такой же, как и другие).
@Wardy Для получения более подробной информации я настоятельно советую вам прочитать страницу en.cppreference.com/w/cpp/language/ub, на которую я ссылаюсь (полностью), а также прочитать все очень хорошие статьи, которые связаны в конце. Это должно дать вам хорошее представление о UB в C++ (и C; но это все еще актуально) и об опасностях, связанных с этим. :-) Современные компиляторы активно эксплуатируют UB и довольно часто оптимизируют на его основе (не происходит) - ИМХО, знать об этом действительно важно.
Ваша помощь оценена по достоинству! Я дам им чек :)
@Wardy См. также blog.regehr.org/archives/213 (а также части 2 и 3).
Раньше я делал что-то подобное в одном из своих API, но мне пришлось это изменить. Проблема в том, что компилятор может предположить, что всякий раз, когда вы вызываете
x->y()
, этоx
всегда будет не NULL, поэтому любые проверки на достоверность указательthis
, который вы, возможно, захотите сделать внутриy()
, может быть и, скорее всего, будет оптимизирован за ненадобностью. Единственный способ обеспечить надежную работу такого рода тестов — сделать их статическим методом или отдельной функцией (например,bool IsValidLowLevel(const Actor * actorPtr) {return (actorPtr != NULL);}
).