What's a good pattern to calculate a variable only when it is used the first time?
C++C++ Problem Overview
Not an actual problem but I'm looking for a pattern to improve the following logic:
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);
}
This code works, but the issue I have is that in most cases there aren't any gamemasters
players so the message composition will be done for nothing.
I'd like to write something that would only create the message on the first use of that variable, but I can't come up with a good solution here.
EDIT:
To make this question more precise, I'm looking for a solution that's not specific to strings, it could be a type without a function to test if it's initialized.
Also big bonus points if we can keep the call to GetComplicatedDebugMessage
at the top of the loop, I think a solution involving a wrapper would solve this.
C++ Solutions
Solution 1 - C++
Whereas std::string
has empty value which might mean "not computed", you might use more generally std::optional
which handle empty string and non default constructible types:
void PrintToGameMasters()
{
std::optional<std::string> message;
for (Player* player : GetAllPlayers()) {
if (player->IsGameMaster()) {
if (!message) {
message = GetComplicatedDebugMessage();
}
player->SendMessage(*message);
}
}
}
Solution 2 - C++
Use data-oriented design; keep two lists of players: game masters and non-game masters (or all players like you have now + a separate vector of pointers to game-masters only).
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);
}
}
}
The goal is to minimize if
-statements inside loops.
Optimize for the most common case, not the most generic one; the most common case is that a player is not a game master; so avoid looping over them.
P.S. Since you're developing a game, I want to add this link to Mike Acton's cppcon talk which you might find interesting.
Solution 3 - C++
Some good ideas here, but I like to keep it a bit more simple:
void PrintToGameMasters()
{
std::string message;
for (Player* player : GetAllPlayers())
{
if (player->IsGameMaster())
{
if (message.empty())
message = GetComplicatedDebugMessage();
player->SendMessage(message);
}
}
}
Everybody can follow this, and it's cheap as chips… plus it's easy as pie to debug.
Solution 4 - C++
You can use std::call_once
with a lambda to call the function the first time you find a game master like
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);
}
}
Solution 5 - C++
Wrap the message in a mutable lambda:
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());
Solution 6 - C++
You can extend the approach of using an std::optional
(as in Jarod41's answer) with lazy evaluation on top. This would also fulfill the requirement to "keep the call to GetComplicatedDebugMessage
at the top of the loop".
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);
}
Solution 7 - C++
Not sure if this is the best pattern, but you can delay computation with a lambda:
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());
}
Solution 8 - C++
I'm not sure why you want to keep the definition of message
above the loop. If someone's reading the code to analyze what happens in the case where std::end(GetAllPlayers())==std::begin(GetAllPlayers)()
, you don't want to clutter their mental workspace with irrelevant variables.
If you're willing to give that up, then static
is your friend:
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);
}
}
Solution 9 - C++
This is literally one of the things std::future
is designed to solve:
void PrintToGameMasters()
{
auto message = std::async(
std::launch::deferred,
[]{return GetComplicatedDebugMessage();}
);
for (Player* player : GetAllPlayers())
if (player->IsGameMaster())
player->SendMessage(message.get());
}
Invoking std::async
with std::launch::deferred
causes the task to be “executed on the calling thread the first time its result is requested”.
Solution 10 - C++
This works. As the MIT license would put it:
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED
#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;
}
It is much faster than the if (!message) message = GetMessage()
approach, unless you have a CPU with a branch predictor (which you likely do). In that case, it's slower (or maybe equally fast, but not faster), uglier, less portable, and will likely get you killed by a psychopath.
Solution 11 - C++
You can use a custom local type with a conversion operator:
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);
}
Of course, this assumes that GetComplicatedDebugMessage
is in fact stateless. Otherwise, you'll need to resort to capturing a lambda, or similar tricks described in the other answers here.
Solution 12 - C++
Really hope this helps
Try maybe implementing this logic:
#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;
}
With Response:
First Time Old News Old News Old News Old News Old News
Solution 13 - C++
static
variables are initialized on first time through. So:
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);
}
}
This is, of course, assuming that calculating the value once at most (rather than once per call at most) is a valid way of proceeding. It's not clear from how you phrased your task whether this is the case.