Какой хороший шаблон для расчета переменной только при первом использовании? [закрыто]

Не является реальной проблемой, но я ищу шаблон для улучшения следующей логики:

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 в верхней части цикла, я думаю, что решение, включающее обертку, решит эту проблему.

51
задан Mefitico 16 August 2019 в 10:32
поделиться

13 ответов

Принимая во внимание, что 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);
       }
    }
}
77
ответ дан 7 November 2019 в 09:41
поделиться

Используйте ориентированный на данные на дизайн ; сохраните два списка плееров: игровые ведущие устройства и неигровые ведущие устройства (или все плееры как Вы имеют теперь + отдельный вектор указателей только на преподавателей физкультуры).

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 - операторы в циклах.

Оптимизируют для наиболее распространенного случая, не самого универсального; наиболее распространенный случай - то, что игрок не является игровым ведущим устройством; поэтому избегайте цикличного выполнения по ним.

<час>

P.S., Так как Вы разрабатываете игру, я хочу добавить эту ссылку Mike Acton разговор о cppcon , который Вы могли бы найти интересным.

38
ответ дан 7 November 2019 в 09:41
поделиться

Можно использовать 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);
       }
}
30
ответ дан 7 November 2019 в 09:41
поделиться

Некоторые хорошие идеи здесь, но мне нравится сохранять это немного более простым:

void PrintToGameMasters()
{
    std::string message;

    for (Player* player : GetAllPlayers())
    {
       if (player->IsGameMaster())
       {
           if (message.empty())
              message = GetComplicatedDebugMessage();

           player->SendMessage(message);
       }
    }
}

Все могут следовать за этим, и это дешево как chips… плюс он легок как круг отладить.

36
ответ дан 7 November 2019 в 09:41
поделиться

Перенесите сообщение в изменяемую лямбду:

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());
13
ответ дан 7 November 2019 в 09:41
поделиться

Не уверенный, если это - лучший шаблон, но можно задержать вычисление с лямбдой:

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());
}
4
ответ дан 7 November 2019 в 09:41
поделиться

Можно расширить подход использования 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);
}
4
ответ дан 7 November 2019 в 09:41
поделиться

Я не уверен, почему Вы хотите сохранить определение 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);
        }
}
2
ответ дан 7 November 2019 в 09:41
поделиться

Это работает. Как лицензия MIT выразилась бы:

ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЕНО "AS", БЕЗ ГАРАНТИИ ЛЮБОГО ДОБРОГО, ЯВНОГО ИЛИ ПОДРАЗУМЕВАЕМОГО

#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;
}

code working as intended

, Это намного быстрее , чем эти if (!message) message = GetMessage() подход, если у Вас нет ЦП с предиктором ответвления (который Вы, вероятно, делаете). В этом случае это медленнее (или возможно одинаково быстро, но не быстрее), более ужасно, менее портативно, и будет, вероятно уничтожать Вас психопатом .

1
ответ дан 7 November 2019 в 09:41
поделиться

Это - буквально одна из вещей 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 with std::launch::deferred причины задача быть “executed на вызывающем потоке в первый раз его результатом является requested”.

1
ответ дан 7 November 2019 в 09:41
поделиться

Можно использовать пользовательский локальный тип с оператором преобразования:

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 является на самом деле не сохраняющим состояние. Иначе необходимо будет обратиться к получению лямбды или подобных приемов, описанных в других ответах здесь.

1
ответ дан 7 November 2019 в 09:41
поделиться

Действительно надежда это помогает

Попытка, возможно, реализовывая эту логику:

#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 
-1
ответ дан 7 November 2019 в 09:41
поделиться

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);
       }
}

Это, конечно, предполагает, что вычисление значения однажды самое большее (а не однажды на вызов самое большее) является допустимым способом продолжиться. Не ясно из того, как Вы формулировали свою задачу ли дело обстоит так.

-1
ответ дан 7 November 2019 в 09:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: