What is wrong with this solution for MSVC with double check for error blocking and statics function?

It is not entirely clear why this does not work. The managed entity is still being created twice:

/** Returns an object with static storage duration.
    This is a workaround for Visual Studio 2013 and earlier non-thread
    safe initialization of function local objects with static storage duration.

    Usage:
    @code
    my_class& foo()
    {
        static static_initializer <my_class> instance;
        return *instance;
    }
    @endcode
*/
template <
    class T,
    class Tag = void
>
class static_initializer
{
private:
    T* instance_;

public:
    template <class... Args>
    explicit static_initializer (Args&&... args);

    T&
    get() noexcept
    {
        return *instance_;
    }

    T&
    operator*() noexcept
    {
        return get();
    }

    T*
    operator->() noexcept
    {
        return &get();
    }
};

template <class T, class Tag>
template <class... Args>
static_initializer <T, Tag>::static_initializer (Args&&... args)
{
#ifdef _MSC_VER
    static std::aligned_storage <sizeof(T),
        std::alignment_of <T>::value>::type storage;
    instance_ = reinterpret_cast<T*>(&storage);

    // Double checked lock:
    //  0 = unconstructed
    //  1 = constructing
    //  2 = constructed
    //
    static long volatile state; // zero-initialized
    if (state != 2)
    {
        struct destroyer
        {
            T* t_;
            destroyer (T* t) : t_(t) { }
            ~destroyer() { t_->~T(); }
        };

        for(;;)
        {
            long prev;
            prev = InterlockedCompareExchange(&state, 1, 0);
            if (prev == 0)
            {
                try
                {
                    ::new(instance_) T(std::forward<Args>(args)...);                   
                    static destroyer on_exit (instance_);
                    InterlockedIncrement(&state);
                }
                catch(...)
                {
                    InterlockedDecrement(&state);
                    throw;
                }
            }
            else if (prev == 1)
            {
                std::this_thread::sleep_for (std::chrono::milliseconds(10));
            }
            else
            {
                assert(prev == 2);
                break;
            }
        }
    }

#else
    static T object(std::forward<Args>(args)...);
    instance_ = &object;

#endif
}
+4
source share
2 answers

, . . , Visual Studio 2013 - bool. bool true . , , . , , , get() .

get() , . , -.

, Visual Studio, ++ 11, 2013 , - , , :

void example()
{
    static MyObject foo;
    foo.bar();
}

void example()
{
    beast::static_initializer <MyObject> foo;
    foo->bar();
}

. :

#ifndef BEAST_UTILITY_STATIC_INITIALIZER_H_INCLUDED
#define BEAST_UTILITY_STATIC_INITIALIZER_H_INCLUDED

#include <beast/utility/noexcept.h>
#include <utility>

#ifdef _MSC_VER
#include <cassert>
#include <chrono>
#include <new>
#include <type_traits>
#include <intrin.h>
#endif

namespace beast {

/** Returns an object with static storage duration.
    This is a workaround for Visual Studio 2013 and earlier non-thread
    safe initialization of function local objects with static storage duration.

    Usage:
    @code
    my_class& foo()
    {
        static static_initializer <my_class> instance;
        return *instance;
    }
    @endcode
*/
#ifdef _MSC_VER
template <
    class T,
    class Tag = void
>
class static_initializer
{
private:
    struct data_t
    {
        //  0 = unconstructed
        //  1 = constructing
        //  2 = constructed
        long volatile state;

        typename std::aligned_storage <sizeof(T),
            std::alignment_of <T>::value>::type storage;
    };

    struct destroyer
    {
        T* t_;
        explicit destroyer (T* t) : t_(t) { }
        ~destroyer() { t_->~T(); }
    };

    static
    data_t&
    data() noexcept;

public:
    template <class... Args>
    explicit static_initializer (Args&&... args);

    T&
    get() noexcept;

    T&
    operator*() noexcept
    {
        return get();
    }

    T*
    operator->() noexcept
    {
        return &get();
    }
};

//------------------------------------------------------------------------------

template <class T, class Tag>
auto
static_initializer <T, Tag>::data() noexcept ->
    data_t&
{
    static data_t _; // zero-initialized
    return _;
}

template <class T, class Tag>
template <class... Args>
static_initializer <T, Tag>::static_initializer (Args&&... args)
{
    data_t& _(data());

    // Double Checked Locking Pattern

    if (_.state != 2)
    {
        T* const t (reinterpret_cast<T*>(&_.storage));

        for(;;)
        {
            long prev;
            prev = InterlockedCompareExchange(&_.state, 1, 0);
            if (prev == 0)
            {
                try
                {
                    ::new(t) T (std::forward<Args>(args)...);                   
                    static destroyer on_exit (t);
                    InterlockedIncrement(&_.state);
                }
                catch(...)
                {
                    // Constructors that throw exceptions are unsupported
                    std::terminate();
                }
            }
            else if (prev == 1)
            {
                std::this_thread::yield();
            }
            else
            {
                assert(prev == 2);
                break;
            }
        }
    }
}

template <class T, class Tag>
T&
static_initializer <T, Tag>::get() noexcept
{
    data_t& _(data());
    for(;;)
    {
        if (_.state == 2)
            break;
        std::this_thread::yield();
    }
    return *reinterpret_cast<T*>(&_.storage);
}

#else
template <
    class T,
    class Tag = void
>
class static_initializer
{
private:
    T* instance_;

public:
    template <class... Args>
    explicit
    static_initializer (Args&&... args);

    T&
    get() noexcept
    {
        return *instance_;
    }

    T&
    operator*() noexcept
    {
        return get();
    }

    T*
    operator->() noexcept
    {
        return &get();
    }
};

template <class T, class Tag>
template <class... Args>
static_initializer <T, Tag>::static_initializer (Args&&... args)
{
    static T t (std::forward<Args>(args)...);
    instance_ = &t;
}

#endif

}

#endif

//------------------------------------------------------------------------------

#include <beast/utility/static_initializer.h>
#include <beast/unit_test/suite.h>
#include <atomic>
#include <condition_variable>
#include <sstream>
#include <thread>
#include <utility>

namespace beast {

static_assert(__alignof(long) >= 4, "");

class static_initializer_test : public unit_test::suite
{
public:
    // Used to create separate instances for each test
    struct cxx11_tag { };
    struct beast_tag { };
    template <std::size_t N, class Tag>
    struct Case
    {
        enum { count = N };
        typedef Tag type;
    };

    struct Counts
    {
        Counts()
            : calls (0)
            , constructed (0)
            , access (0)
        {
        }

        // number of calls to the constructor
        std::atomic <long> calls;

        // incremented after construction completes
        std::atomic <long> constructed;

        // increment when class is accessed before construction
        std::atomic <long> access;
    };

    /*  This testing singleton detects two conditions:
        1. Being accessed before getting fully constructed
        2. Getting constructed twice
    */
    template <class Tag>
    class Test;

    template <class Function>
    static
    void
    run_many (std::size_t n, Function f);

    template <class Tag>
    void
    test (cxx11_tag);

    template <class Tag>
    void
    test (beast_tag);

    template <class Tag>
    void
    test();

    void
    run ();
};

//------------------------------------------------------------------------------

template <class Tag>
class static_initializer_test::Test
{
public:
    explicit
    Test (Counts& counts);

    void
    operator() (Counts& counts);
};

template <class Tag>
static_initializer_test::Test<Tag>::Test (Counts& counts)
{
    ++counts.calls;
    std::this_thread::sleep_for (std::chrono::milliseconds (10));
    ++counts.constructed;
}

template <class Tag>
void
static_initializer_test::Test<Tag>::operator() (Counts& counts)
{
    if (! counts.constructed)
        ++counts.access;
}

//------------------------------------------------------------------------------

template <class Function>
void
static_initializer_test::run_many (std::size_t n, Function f)
{
    std::mutex mutex;
    std::condition_variable cond;
    std::atomic <bool> start (false);
    std::vector <std::thread> threads;

    threads.reserve (n);

    {
        std::unique_lock <std::mutex> lock (mutex);
        for (auto i (n); i-- ;)
        {
            threads.emplace_back([&]()
            {
                {
                    std::unique_lock <std::mutex> lock (mutex);
                    while (! start.load())
                        cond.wait(lock);
                }

                f();
            });
        }
        start.store (true);
    }
    cond.notify_all();
    for (auto& thread : threads)
        thread.join();
}

template <class Tag>
void
static_initializer_test::test (cxx11_tag)
{
    testcase << "cxx11 " << Tag::count << " threads";

    Counts counts;

    run_many (Tag::count, [&]()
    {
        static Test <Tag> t (counts);
        t(counts);
    });

#ifdef _MSC_VER
    // Visual Studio 2013 and earlier can exhibit both double
    // construction, and access before construction when function
    // local statics are initialized concurrently.
    //
    expect (counts.constructed > 1 || counts.access > 0);

#else
    expect (counts.constructed == 1 && counts.access == 0);

#endif
}

template <class Tag>
void
static_initializer_test::test (beast_tag)
{
    testcase << "beast " << Tag::count << " threads";

    Counts counts;

    run_many (Tag::count, [&counts]()
    {
        static static_initializer <Test <Tag>> t (counts);
        (*t)(counts);
    });

    expect (counts.constructed == 1 && counts.access == 0);
}

template <class Tag>
void
static_initializer_test::test()
{
    test <Tag> (typename Tag::type {});
}

void
static_initializer_test::run ()
{
    test <Case<  4, cxx11_tag>> ();
    test <Case< 16, cxx11_tag>> ();
    test <Case< 64, cxx11_tag>> ();
    test <Case<256, cxx11_tag>> ();

    test <Case<  4, beast_tag>> ();
    test <Case< 16, beast_tag>> ();
    test <Case< 64, beast_tag>> ();
    test <Case<256, beast_tag>> ();
}

//------------------------------------------------------------------------------

BEAST_DEFINE_TESTSUITE(static_initializer,utility,beast);

}
+2

, Microsoft magic statics VS2013, std::call_once . , .

class Instance
{
  public:
      Instance& GetInstance() {
          std::call_once(m_Flag, [] { m_Instance.reset(new Instance); } );
          return *m_Instance.get();
      }
  private:
      static std::unique_ptr<Instance> m_Instance;
      static std::once_flag m_Flag;
      Instance(void);
};
0

Source: https://habr.com/ru/post/1548671/


All Articles