惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
About on SuperTechFans
Y
Y Combinator Blog
V
V2EX
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog
博客园 - 叶小钗
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
H
Help Net Security
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
B
Blog
G
Google Developers Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
N
Netflix TechBlog - Medium
腾讯CDC

The Old New Thing

Why do Microsoft job levels start in the high 50's instead of starting at a sane number like 1? - The Old New Thing Why didn't Read­Directory­ChangesW provide a way to correlate the two sides of a rename operation? - The Old New Thing How can I remove the Close button from my window caption? - The Old New Thing Why is the x86 undefined instruction called ud2? Why 2? - The Old New Thing What algorithm did Windows XP use to choose your initial user picture? - The Old New Thing A sample use of the winstart.bat file in Windows 95 - The Old New Thing Why don't we allow stacks to be sparse, instead of forcing them to be contiguous? - The Old New Thing What happens if you change a window class's GCL_CB­WND­EXTRA? - The Old New Thing The case of the progress callback that never got called when progress happened - The Old New Thing The perils of binding to value types in XAML - The Old New Thing Microspeak: Funded / unfunded - The Old New Thing AWE does not require PAE, though PAE makes it much more useful - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 2 - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 1 - The Old New Thing In the product end game, every change carries significant risk, episode 2 - The Old New Thing Why didn't the Windows Entertainment Pack just run the MS-DOS version inside an emulator? - The Old New Thing Comparing the two holograms on the Windows 95 box - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function, practical exam - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function - The Old New Thing On wrapping a callable in a lambda that just calls it with the same parameters - The Old New Thing Why did the Microsoft Entertainment Pack for Windows have a special sticker announcing that it also had Tetris? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing Forcing an ARM64X executable to run as a specific architecture - The Old New Thing A little helper class for managing LPPROC_THREAD_ATTRIBUTE_LISTs - The Old New Thing The comments that go into code versus those that go into the pull request description - The Old New Thing The little-known winstart.bat batch file - The Old New Thing How can I perform a Copy­File&shy in unbuffered mode? - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5 - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4 - The Old New Thing
Magic statics vs. std::call_once - The Old New Thing
Raymond Chen · 2026-09-16 · via The Old New Thing

Suppose you have some function like

bool should_use_widgets()
{
    bool supported = ⟦ complex code to check OS features ⟧;
    return supported && is_configuration_enabled("widgets");
}

Since OS Widget support is not something that changes during the lifetime of the program, you want to calculate it once and cache the result.

One way is to use a so-called “magic static”:

bool should_use_widgets()
{
    static const bool supported = [] {
        return ⟦ complex code to check OS features ⟧;
    }();
    return supported && is_configuration_enabled("widgets");
}

Function-local statics are initialized the first time execution reaches the variable. On subsequent executions, nothing happens.

Another way is to use std::call_once.

bool is_supported_cached;
std::once_flag is_supported_once;

bool are_widgets_supported()
{
    std::call_once(is_supported_once, [] {
        is_supported_cached = ⟦ complex code to check OS features ⟧;
    });
    return is_supported_cached && is_configuration_enabled("widgets");
}

Why would you choose one over the other?

Magic statics are certainly more convenient. You don’t have to juggle two variables. You just declare a function-local static and initialize it. One problem is that they have to be a function-local static. Multiple functions can’t access that same cached variable. But that’s easy to work around: Have a function whose sole job is to manage that one static.

bool are_widgets_supported_in_os()
{
    static const bool supported = [] {
        return ⟦ complex code to check OS features ⟧;
    }();
    return supported;
}

bool are_widgets_supported()
{
    return are_widgets_supported_in_os() &&
        is_configuration_enabled("widgets");
}

bool are_widget_carriers_supported()
{
    return are_widgets_supported_in_os() &&
        is_configuration_enabled("widget_carriers");
}

This trick is often used for singleton patterns.

class Singleton
{
public:
    static Singleton& GetInstance()
    {
        static Singleton instance;
        return instance;
    }

    ⟦ various methods go here ⟧;

private:
    Singleton() = default;
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;
    ~Singleton() = default;
}

So when would you use call_once?

Magic statics work only for statics. Maybe you want to lazy-initialize a non-static data member.

Suppose we have a Gadget that is constructed with an associated Widget. And suppose that the Gadget support for polarity reversal is dependent on whether the Widget supports polarity reversal. Furthermore, polarity reversibility is expensive to calculate, but since it is an immutable property, we can calculate it only once and cache the result.

class Gadget
{
public:
    Gadget(std::shared_ptr<Widget> const& widget) : widget(widget) {}

    bool can_reverse_polarity()
    {
        return can_reverse_polarity_cached;
    }

private:
    std::shared_ptr<Widget> const widget;
    bool can_reverse_polarity_cached =
        is_configuration_enabled("polarity_reversal") &&
        is_widget_polarity_reversible(*widget);
};

The can_reverse_polarity_cached is a non-static data member with an explicit initializer, so it initializes at the construction of the Gadget class, rather than initializing on demand the first time somebody calls can_reverse_polarity.

“No problem,” you say. “I can use a magic static.”

    bool can_reverse_polarity()
    {
        static bool can_reverse_polarity_cached =           
            is_configuration_enabled("polarity_reversal") &&
            is_widget_polarity_reversible(*widget);         

        return can_reverse_polarity_cached;
    }

Function-static variables in a member function are static with respect to the member function. All instances of Gadget share the same member function, and therefore they all share the same can_reverse_polarity_cached variable. The time you call Gadget::can_reverse_polarity(), it calculates the reversibility of the Widget that is associated with the Gadget you called it from, and that value is then locked in for all future calls to Gadget::can_reverse_polarity(), even though the future calls may be on unrelated Gadgets.

What we want is a variant of magic statics that initialize for each instance of the class, rather than once for all instances.

That’s the case for std::call_once.

class Gadget
{
public:
    Gadget(std::shared_ptr<Widget> const& widget) : widget(widget) {}

    bool can_reverse_polarity()
    {
        std::call_once(can_reverse_polarity_once, [] {          
            can_reverse_polarity_cached =                       
                is_configuration_enabled("polarity_reversal") &&
                is_widget_polarity_reversible(*widget);         
        });                                                     
        return can_reverse_polarity_cached;
    }

private:
    std::shared_ptr<Widget> const widget;
    bool can_reverse_polarity_cached; // initializes on demand
    std::once_flag can_reverse_polarity_once;                 
};

I guess you could encapsulate this in a lazy<T> type.¹

template<typename T, typename L>
struct lazy
{
    lazy(L&& l) : init(std::forward<L>(l)) {}

    T& get() {
        std::call_once(once, [&] {
            value.emplace(init());
        });
        return *value;
    }
private:
    std::optional<T> value;
    std::once_flag once;
    std::decay_t<L> init;
};

template<typename T, typename L>
lazy<T, L> make_lazy(L&& l)
{
    return { std::forward<L>(l) };
}

void test()
{
    auto v = make_lazy<int>([] {
        printf("Slow calculation\n");
        return 42;
    });

    printf("Value is %d\n", v.get());
    printf("Value is still %d\n", v.get());
}

But wait, we also have std::async with deferred execution. Should we use that? We’ll look at this question next time.

¹ Note that this is not the same as the std::lazy proposal.

Category

Topics

Author

Raymond Chen

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.