#c
#c
Вопрос:
Приветствую коллег-программистов.
Я боролся с изучением c в Unreal engine. Я думал, что понял, как правильно отслеживать время внутри класса, но, увы, моя переменная изменяет свое содержимое на совершенно другое число за время между двумя вызовами функции.
Для контекста присутствует небольшое количество объектов:
- Глобальная система времени
- Этот класс отвечает за управление временем и получение тактов обновления от наблюдателя за временем. Это тоже синглтон!
- TimeWatcher
- Супер просто, просто Uobject, который я запускаю в мир, чтобы он мог получать обновления от движка и передавать их в глобальную систему времени
- Класс времени
- Класс для хранения часов, минут и секунд. То, как оно используется после этого, зависит от разработчика, использующего класс. В моем случае я просто пытаюсь сохранить его и с этого момента удалить из него время, чтобы создать таймер обратного отсчета.
У нас есть наша собственная небольшая система ведения журнала, которая помогает в отладке, в основном для генерации журналов без всего этого нереального материала и в формате, который мы предпочитаем. В этом журнале выводятся следующие данные:
<Log, TimerSystem> [2] 2019.03.17-17.41.42: init attempt, init time should be: 23:6.0
<Log, TimerSystem> [3] 2019.03.17-17.41.42: init attempt succes, 23:6.0
<Log, TimerSystem> [6] 2019.03.17-17.41.42: Timer tick occured, lets see what our timer thinks about the time -225161083:32766:00
Итак, исходя из этого, мы можем интерпретировать, что переменная в области видимости, в которой она устанавливается (показано ниже), установлена там правильно. Но в тот момент, когда мы пытаемся прочитать это снова в функции handleTick, переменная оказывается неверной.
Функция InitTimer:
void GlobalTimerSystem::InitTimer(UWorld* world, Time* initialTime)
{
DebugUtility::WriteLog("init attempt, init time should be: " initialTime->ToString(), DebugUtility::Log, DebugUtility::TimerSystem);
check(world);
//create timeWatcher in the world
FVector location(0.0f, 0.0f, 0.0f);
FRotator rotation(0.0f, 0.0f, 0.0f);
FActorSpawnParameters SpawnInfo;
world->SpawnActor<ATimeWatcher>(location, rotation, SpawnInfo);
//set current time to init value
Time* trPointer = new Time(initialTime->GetHours(), initialTime->GetMinutes(), initialTime->GetSeconds());
this->timeRemaining = *trPointer;
DebugUtility::WriteLog("init attempt succes, " timeRemaining.ToString(), DebugUtility::Log, DebugUtility::TimerSystem);
}
Я здесь занимаюсь какой-то глупой ерундой с указателями, отчасти из-за отчаяния на данный момент.
Функция дескриптора tick:
void GlobalTimerSystem::HandleTimerTick(float deltaTime)
{
DebugUtility::WriteLog("Timer tick occured, lets see what our timer thinks about the time " timeRemaining.ToString(), DebugUtility::Log, DebugUtility::TimerSystem);
ticksReceived ;
FString debug2;
debug2.Append("Ticks received: ");
debug2.AppendInt(ticksReceived);
DebugUtility::WriteLog(debug2, DebugUtility::Log, DebugUtility::TimerSystem);
double t = static_cast<double>(deltaTime);
DecreaseTimer(amp;t);
if (deltaTime != NULL) {
FString debug;
debug.Append(TEXT("current time remaining is "));
debug.Append(*timeRemaining.ToString());
DebugUtility::WriteLog(debug, DebugUtility::Log, DebugUtility::TimerSystem);
}
}
Теперь мы знаем, что все уже неправильно в тот момент, когда мы вводим вышеупомянутую функцию. Для хорошей оценки вот файл заголовка для этого класса.
class PGT_PROJECT_API GlobalTimerSystem
{
friend class ATimeWatcher;
private:
Time timeRemaining;
Time timeElapsedNotPaused;
Time timeElapsedPaused;
UINT ticksReceived = 0;
bool paused = false;
bool initComplete = false;
void HandleTimerTick(float deltaTime);
static GlobalTimerSystem* timerSystem;
public:
static GlobalTimerSystem* GetTimerSystem();
void InitTimer(UWorld* world, Time* initialTime);
void IncreaseTimer(double* additionalSeconds);
void DecreaseTimer(double* removeSeconds);
double GetTimeRemaining();
double GetTimeElapsed();
GlobalTimerSystem();
~GlobalTimerSystem();
};
Если потребуется дополнительная информация, я буду рад предоставить. Спасибо, что уделили мне время!
Редактировать:
Я перегружаю Time::operator=, который выглядит следующим образом:
Time amp; Time::operator=(Time amp; t)
{
_seconds = t._seconds;
_minutes = t._minutes;
_hours = t._hours;
return *this;
}
И использовать его следующим образом:
this->timeRemaining = Time(initialTime->GetHours(), initialTime->GetMinutes(), initialTime->GetSeconds());
Однако это приводит к следующей ошибке компилятора, которую я не понимаю:
Path...PrivateGlobalTimerSystem.cpp(62) : error C4239: nonstandard extension used: 'argument': conversion from 'Time' to 'Time amp;'
Комментарии:
1. Есть ли что-то, что я упускаю из виду, или есть серьезное несоответствие типа между
GlobalTimerSystem::timeRemaining
(типаTime
) иtrPointer
(типаTime*
) в присваиванииthis->timeRemaining = trPointer;
? Но я озадачен, потому что это то, на что компилятор определенно будет жаловаться…2. @PiCTo Хм, да, вы правы, это наверняка неверно. Я добавил к нему ссылку, чтобы посмотреть, изменилось ли поведение вообще. К сожалению, ошибка не в этом. Но, подумав немного дальше, я перегрузил оператор =, чтобы иметь возможность это сделать. Я решил удалить код и оставить разыменование. Все выглядит лучше, однако после инициализации мой таймер, который должен показывать 23: 6.0, выдает результат 0.0
3. Приоритет оператора:
*timeRemaining.ToString()
анализируется как*(timeRemaining.ToString())
. Таким образом, ваше значениеtimeRemaining
преобразуется в строку, а затем применяется*
, который, предположительно, извлекает первый символ.4. @RaymondChen Я сомневаюсь, что здесь проблема. Я вижу, что это работает должным образом в первых двух запусках, где я точно знаю, что находится в переменной.
5.@TDSrock Соответствующий параметр
operator=
должен приниматьconst
параметр (здесь,t
). В любом случае, если это ваша перегруженная функцияoperator=
, она не делает ничего, кроме значения поoperator=
умолчанию (т. е.,, мелкая копия), и вам это не нужно. Глядя на общую картину, ваш дизайн кажется более сложным, чем должен быть: вы хотите отслеживать время, и самым простым способом сделать это было бы иметь один счетчик, например, для секунд (часы и минуты могут быть преобразованы в секунды). Затем вы управляете одним встроенным типом, который будет для вас гораздо менее подвержен ошибкам, основываясь на ваших текущих способностях в C .
Ответ №1:
В GlobalTimerSystem::InitTimer(UWorld*, Time*)
вы делаете следующее:
Time* trPointer = new Time(initialTime->GetHours(),
initialTime->GetMinutes(),
initialTime->GetSeconds());
this->timeRemaining = trPointer;
что означает:
- Создайте
new
объект типаTime
в куче, сконструируйте его со следующими аргументами и, как только он будет готов, верните указатель на него (Time*
), который я сохраню в своей локальной переменнойtrPointer
; - присвойте значение указателя
trPointer
(которое является адресом экземпляра классаTime
, который мы только что выделили и инициализировали в куче) моей переменной экземпляраtimeRemaining
(которая является экземпляром классаTime
).
Итак, как только вы достигнете GlobalTimerSystem::HandleTimerTick
, this->timeRemaining
содержит мусор, который остается мусором при переводе ToString
(отсюда -225161083:32766:00
то, что вы видите). Более того, память, которую вы сейчас выделили в куче для этого созданного вами экземпляра Time
, тратится впустую, поскольку вы никогда не освободите ее и даже не будете использовать.
Дело в том, что в этом случае вам вообще не нужна куча!
В зависимости от того, как operator=
себя ведет (вы сказали, что перегрузили его), вы должны быть в состоянии сделать:
this->timeRemaining = Time(initialTime->GetHours(),
initialTime->GetMinutes(),
initialTime->GetSeconds());
которая создаст временный Time
экземпляр и инициализирует его переданными аргументами, затем «скопирует» его ( =
) внутри вашей переменной экземпляра timeRemaining
. Если вы сделаете это, возможно, вам захочется изучить, Time::operator=(Timeamp;amp;)
поскольку этот «временный Time
экземпляр» является значением rvalue. Пожалуйста, обратите внимание, что в этом случае мы не допускаем утечки памяти, поскольку все выделено в стеке и будет освобождено при возврате функции.
Если это не работает, это означает, что Time::operator=
не работает как надлежащий «оператор копирования» и должно быть исправлено. Другим подходом было бы вручную задать поля часов, минут и секунд timeRemaining
(если они есть public
или friend
) или (что намного лучше), чтобы иметь такой метод, как Time::set(/*hours, minutes, seconds*/)
позволяющий вам this->timeRemaining->set(...)
.
Наконец, еще раз в зависимости от того, что Time
и как Time::operator=
было написано, заметив, что initialTime
само по себе является Time*
, «временный промежуточный Time
экземпляр» даже не должен быть нужен, что приводит к гораздо более простому и читабельному
this->timeRemaining = *initialTime;
В заключение, я полагаю, что ваша проблема связана с реализацией Time::operator=
.
Комментарии:
1. Вы могли бы быть абсолютно правы в том, что проблема заключается в моей перегрузке оператора = . Есть ли у вас какие-либо примеры оператора =, который должным образом перегружен? Я не смог найти ни одной в Интернете. (Это я полностью понял) По крайней мере, спасибо за информативный пост, это, вероятно, приведет меня к правильному ответу.
2. У меня нет опыта работы с Unreal; Это
Time
ваш собственный класс? Если нет, вы уверены, что у вас есть веская причина для перегрузкиTime::operator=
? Если это так, помогло бы добавление (по крайней мере) ее определения и вашей реализацииoperator=
.3. Я добавлю их в сообщение сейчас. Time — это мой собственный класс, который отделен от Unreal. У Unreal действительно есть класс TimeCode, который похож на этот, но имеет некоторые ограничения, которые вынудили меня создать свой собственный.