C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Use optional<T> as though it were T

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Wed, 28 Jun 2023 09:58:34 +0100
On Wed, Jun 28, 2023 at 1:06 AM Breno GuimarĂ£es wrote:
>
> You can do:
>
> T& g_obj_fn() {
> static T t;
> return t;
> }
>
> #define g_obj (g_obj_fn())
>
> Function local statics do the flag+muted+call_once for you.


That's exactly what I have in my current code, I'm just looking for
ways to make it more efficient. For example, the 'call_once' can be
taken out if we change the global function pointer. I've come up with
three changes:
(1) Mark the global function pointer as 'thread_local'
(2) Use a shared_mutex instead of a mutex
(3) Only lock the mutex the first time for each thread

I've gotten the machine code for the function "Func_PostInit" down to:

        mov rax, OFFSET FLAT:g_optional
        ret

And the machine code for invoking the thread_local function pointer is:

        mov rax, QWORD PTR fs:g_funcptr_at_tpoff
        call rax

I might be very very close to the most efficient implementation.

So here's my code now:

    https://godbolt.org/z/sPfPsnbjv

And here it is copy-pasted:

#include <atomic> // atomic
#include <optional> // optional
#include <mutex> // once_flag, call_once, lock_guard
#include <shared_mutex> // shared_mutex, shared_lock
#include <iostream> // Just for testing: cout, enld
#include <thread> // Just for testing: jthread,
get_id, sleep_for
#include <chrono> // Just for testing: milliseconds

struct T {
     T(void) { std::cout << "Constructing. . .\n"; }
    ~T(void) { std::cout << "Destroying. . .\n" ; }
    void Speak(void) { std::cout << "Hi!\n"; }
};

std::optional<T> g_optional;

/* forward declaration of function */ T &Func_PreInit(void);

/* Note that the following variable is thread_local */
thread_local std::atomic<T &(*)(void)> g_funcptr{ &Func_PreInit }; /*
<<<<<<<<<<<< thread_local */

static std::shared_mutex g_mutex_for_optional;

T &Func_PostInit(void)
{
    // No protection at all in here -- no mutex, no once_flag, nothing

    std::cout << "- - - Func_PostInit " << std::this_thread::get_id()
<< std::endl;

    return *g_optional.operator->(); // crashes instead of throwing
}

std::once_flag g_flag_for_optional{};

T &Func_PreInit(void)
{
    std::cout << "- - - Func_PreInit " << std::this_thread::get_id()
<< std::endl;

    std::call_once(g_flag_for_optional, []()
        {
            std::lock_guard mylock(g_mutex_for_optional); // exclusive lock
            g_optional.emplace();
        });

    g_funcptr.store( &Func_PostInit ); // ------------- This variable
is thread_local

    std::shared_lock mylock(g_mutex_for_optional); // shared lock
    return *g_optional.operator->(); // crashes instead of throwing
}

#define g_obj (g_funcptr.load()())

int main(void)
{
    g_obj.Speak();
    std::jthread mythread( []()
        {
            std::this_thread::sleep_for( std::chrono::milliseconds(20u) );
            g_obj.Speak();
            std::this_thread::sleep_for( std::chrono::milliseconds(105u) );
            g_obj.Speak();
        } );

    std::this_thread::sleep_for( std::chrono::milliseconds(120u) );
    g_obj.Speak();
    g_obj.Speak();
    g_obj.Speak();
}

I think that might be the most efficient way of implementing a
'construct on demand' global object, allowing for the circumstance
that it never gets constructed.

Received on 2023-06-28 08:58:45