Какой хороший шаблон для вычисления переменной только при первом использовании?
Не актуальная проблема, но я ищу шаблон для улучшения следующей логики:
void PrintToGameMasters()
{
std::string message = GetComplicatedDebugMessage(); // This will create a big string with various info
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(message);
}
Этот код работает, но у меня есть проблема в том, что в большинстве случаев нет игроков gamemasters
, поэтому составление сообщения будет сделано даром.
Я хотел бы написать что-то, что могло бы создать сообщение только при первом использовании этой переменной, но я не могу найти здесь хорошего решения.
ОБНОВЛЕНИЕ:
Чтобы сделать этот вопрос более точным, я ищу решение, которое не относится к строкам, это может быть тип без функции, чтобы проверить, инициализируется ли она.
Также большие бонусные баллы, если мы сможем удержать вызов GetComplicatedDebugMessage
на вершине цикла, я думаю, что решение, включающее обертку, решит эту проблему.
Ответы
Ответ 1
Принимая во внимание, что std::string
имеет пустое значение, которое может означать "не вычисляется", вы можете использовать более широко std::optional
, который обрабатывает пустую строку и типы, которые нельзя создать по умолчанию:
void PrintToGameMasters()
{
std::optional<std::string> message;
for (Player* player : GetAllPlayers()) {
if (player->IsGameMaster()) {
if (!message) {
message = GetComplicatedDebugMessage();
}
player->SendMessage(*message);
}
}
}
Ответ 2
Используйте ориентированный на данные дизайн; храните два списка игроков: мастеров игр и неигровых мастеров (или всех игроков, которые есть у вас сейчас + отдельный вектор указателей только для мастеров игр).
void PrintToGameMasters()
{
auto players = GetGameMasters(); // Returns ONLY game master players
if (players.begin() != players.end()) {
std::string message = GetComplicatedDebugMessage();
for (Player* player : players) {
player->SendMessage(message);
}
}
}
Цель - минимизировать if
-statements внутри петель.
Оптимизируйте для наиболее распространенного случая, а не для наиболее общего; наиболее распространенным случаем является то, что игрок не является мастером игры; так что избегайте их зацикливания.
Постскриптум Поскольку вы разрабатываете игру, я хочу добавить эту ссылку в Mike Acton cppcon talk, которая может вас заинтересовать.
Ответ 3
Здесь есть несколько хороших идей, но мне бы хотелось, чтобы это было немного проще:
void PrintToGameMasters()
{
std::string message;
for (Player* player : GetAllPlayers())
{
if (player->IsGameMaster())
{
if (message.empty())
message = GetComplicatedDebugMessage();
player->SendMessage(message);
}
}
}
Каждый может следить за этим, и это дешево, как фишки & hellip; плюс это легко, как пирог для отладки.
Ответ 4
Вы можете использовать std::call_once
с лямбдой для вызова функции при первом обнаружении мастера игры, такого как
void PrintToGameMasters()
{
std::once_flag of;
std::string message;
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
{
std::call_once(of, [&](){ message = GetComplicatedDebugMessage(); });
player->SendMessage(message);
}
}
Ответ 5
Оберните сообщение в изменчивую лямбду:
auto makeMessage = [message = std::string()]() mutable -> std::string&
{
if (message.empty()) {
message = GetComplicatedDebugMessage();
}
return message;
};
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(makeMessage());
Ответ 6
Не уверен, что это лучший шаблон, но вы можете отложить вычисление с помощью лямбды:
void PrintToGameMasters()
{
std::string message = "";
auto getDebugMessage = [&message]() -> const std::string& {
if (message.empty()) {
message = GetComplicatedDebugMessage();
}
return message;
};
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(getDebugMessage());
}
Ответ 7
Вы можете расширить подход, используя std::optional
(как в ответе Jarod41) с ленивой оценкой сверху. Это также удовлетворяет требованию "держать вызов GetComplicatedDebugMessage
на вершине цикла".
template <typename T>
class Lazy : public std::optional<T> {
public:
Lazy(std::function<T()> f) : fn(f) { }
T operator*() {
if (!*this)
std::optional<T>::operator=(fn());
return this->value();
}
private:
std::function<T()> fn;
};
void PrintToGameMasters()
{
Lazy<std::string> message(GetComplicatedDebugMessage);
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(*message);
}
Ответ 8
Я не уверен, почему вы хотите оставить определение message
над циклом. Если кто-то читает код, чтобы проанализировать, что происходит в случае, когда std::end(GetAllPlayers())==std::begin(GetAllPlayers)()
, вы не хотите загромождать свое умственное рабочее пространство нерелевантными переменными.
Если вы готовы отказаться от этого, то static
ваш друг:
void PrintToGameMasters()
{
for (auto const &player : GetAllPlayers())
if (player->IsGameMaster())
{
//Initialization of a static variable occurs exactly once, even when multithreaded,
//precisely when the defining line is hit for the first time
static auto const &message{GetComplicatedDebugMessage()};
player->SendMessage(message);
}
}
Ответ 9
Это работает. Как будет сказано в лицензии MIT:
ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ "КАК ЕСТЬ", БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНЫЕ ИЛИ ПОДРАЗУМЕВАЕМЫЕ
#include <Windows.h>
#include <cstdlib>
#include <cstdio>
#include <string>
struct Player {
bool isGameMaster;
int id;
};
int __stdcall IsGameMaster(Player* self) {
return self->isGameMaster ? 1 : 0;
}
// Could've been "SendMessage"... but Windows.h
void __stdcall SendMessageToPlayer(Player* self, std::string* msg) {
printf("Player %d says: %s\n", self->id - 1000 + 1, msg->c_str());
}
Player g_players[18];
Player* __stdcall GetAllPlayers(void){
return &g_players[0];
}
std::string le_message = "hi, I'm a game master";
std::string* __stdcall GetComplicatedMessage(void) {
puts("GENERATING COMPLICATED MESSAGE. HOGGING CPU FOR 3 DAYS!");
return &le_message; // to make my assembly life easier
}
__declspec(naked) void PrintToGameMasters(void){
__asm {
push ebp;
mov ebp, esp;
sub esp, 8;
call GetAllPlayers;
mov [ebp-4], eax;
// this is 'i', the loop iteration counter
// I chose esi because it is preserved by stdcalls
xor esi, esi;
do_loop:
// Player* player = &g_players[i];
mov ebx, esi;
imul ebx, SIZE Player;
add ebx, [ebp-4]; // ebx = g_players + sizeof(Player) * i, or &g_players[i]
// if (player->IsGameMaster()) {
push ebx;
call IsGameMaster;
test eax, eax;
jz no_print;
// msg = GetComplicatedMessage();
get_msg_start:
call GetComplicatedMessage;
mov [ebp-8], eax;
jmp short delete_self;
get_msg_end:
// player->SendMessage(msg);
push [ebp-8];
push ebx;
call SendMessageToPlayer;
// }
no_print:
inc esi;
cmp esi, 18;
jb do_loop;
mov esp, ebp;
pop ebp;
ret;
delete_self:
mov ecx, get_msg_start;
mov eax, get_msg_end;
sub eax, ecx;
mov byte ptr [ecx], 0xEB; // jmp short
mov byte ptr [ecx+1], al; // relative offset
jmp get_msg_end;
}
}
int main(){
for (int i = 0; i < 18; i++) {
g_players[i].isGameMaster = (i == 12 || i == 15); // players 13 and 16
g_players[i].id = i + 1000;
}
DWORD oldProtect;
VirtualProtect(&PrintToGameMasters, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect);
PrintToGameMasters();
return 0;
}
Это намного быстрее, чем подход if (!message) message = GetMessage()
, если только у вас нет ЦП с предиктором ветвления (что вы, скорее всего, делаете). В этом случае он медленнее (или, возможно, одинаково быстро, но не быстрее), уродливее, менее переносим и, скорее всего, убьет вас психопатом.
Ответ 10
Вы можете использовать пользовательский локальный тип с оператором преобразования:
void PrintToGameMasters()
{
struct {
operator std::string const &(void)
{
static auto const real_value{GetComplicatedDebugMessage()};
return real_value;
}
} message;
for (auto const &player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(message);
}
Конечно, это предполагает, что GetComplicatedDebugMessage
фактически не имеет состояния. В противном случае вам придется прибегнуть к захвату лямбды или аналогичным трюкам, описанным в других ответах здесь.
Ответ 11
Это буквально одна из вещей, которые std::future
предназначена для решения:
void PrintToGameMasters()
{
auto message = std::async(
std::launch::deferred,
[]{return GetComplicatedDebugMessage();}
);
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(message.get());
}
Вызов std::async
с помощью std::launch::deferred
заставляет задачу "выполняться в вызывающем потоке при первом запросе ее результата".
Ответ 12
Очень надеюсь, что это поможет
Попробуйте, возможно, реализовать эту логику:
#include <iostream>
using namespace std;
int main()
{
bool GameMaster,first_time,used_before;
GameMaster = true;
first_time = false;
used_before = false;
GameMaster = first_time;
first_time = used_before;
for( int i = 0; i < 5; i++ ) {
if(GameMaster==used_before) {
cout<<" First Time";
GameMaster = true;
}
if(GameMaster!=used_before) {
cout<<" Old News";
}
}
return 0;
}
с ответом:
First Time Old News Old News Old News Old News Old News
Ответ 13
static
переменные инициализируются впервые. Итак:
void PrintToGameMasters()
{
for (Player* player : GetAllPlayers())
if (player->IsGameMaster()) {
static std::string message = GetComplicatedDebugMessage(); // This will create a big string with various info
player->SendMessage(message);
}
}
Это, конечно, если предположить, что вычисление значения не чаще, чем один раз за вызов, является правильным способом продолжения. Из того, как вы сформулировали свою задачу, не ясно, так ли это.