Why is std::weak_ptr::expired optimized away?

浪子不回头ぞ 提交于 2019-12-03 09:04:36

问题


In the following code, while ( !Ref.expired() ); is joyfully optimized into an infinite loop. If the line of code is changed to while ( !Ref.lock() );. everything works as expected. So two questions really:

1) How can the compiler optimize away expired when std::weak_ptr::expired() accesses a memory-fenced counter?

2) Is Ref.lock() actually safe, or could this too be optimized away?

Sample code below.

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>

class A
{
public:

    A() 
    { 
        m_SomePtr = std::make_shared<bool>( false );
    }

    virtual ~A()
    {
        std::weak_ptr<bool> Ref = m_SomePtr;
        m_SomePtr.reset();

        // Spin (will be optimised into an infinite loop in release builds)
        while ( !Ref.expired() );
    }

    std::shared_ptr<bool> GetPtr() const { return m_SomePtr; }

private:
    std::shared_ptr<bool> m_SomePtr;
};

class B
{
public:
    B( std::shared_ptr<bool> SomePtr ) : m_Ref( SomePtr ) {}

    void LockPtr() { m_SomePtr = m_Ref.lock(); }
    void UnLockPtr() { m_SomePtr.reset(); }

private:
    std::shared_ptr<bool> m_SomePtr;
    std::weak_ptr<bool> m_Ref;
};

int main()
{
    std::unique_ptr<A> a( new A() );
    std::unique_ptr<B> b( new B( a->GetPtr() ) );

    b->LockPtr();

    std::cout << "Starting " << std::endl;

    std::thread first( [&]()
    {
        std::this_thread::sleep_for( std::chrono::seconds( 5 ) );
        b->UnLockPtr();
    } );

    std::thread second( [&]()
    {
        a.reset( nullptr );
    } );

    first.join();
    second.join();

    std::cout << "Complete" << std::endl;
    return 0;
}

回答1:


Your program is incorrect; the shared-ownership pointer facilities are not intended to be used for synchronization.

[intro.multithread]/24:

The implementation may assume that any thread will eventually do one of the following:
— terminate,
— make a call to a library I/O function,
— access or modify a volatile object, or
— perform a synchronization operation or an atomic operation.

std::weak_ptr::expired() is not a synchronization operation or an atomic operation; all the Standard says is that it does not introduce a data race. Since the resolution to Library defect 2316, std::weak_ptr::lock() is considered an atomic operation, so to answer 2) your code using Ref.lock() is valid as of C++14.

Now, it's true that if you were to attempt to create your own library implementation of weak_ptr using the language and library facilities, it would necessarily use the synchronization and/or atomic operation facilities, so a user-provided weak_ptr::expired() would be OK to spin on (the implementation would be obliged to ensure that the thread eventually made progress, per [intro.multithread]/2 and /25). But an implementation is not obliged to restrict its own library to the language and library facilities.

I'm not entirely sure how the compiler is optimizing away the access to expired(). I'd guess that the MSVC library is exploiting aspects of the x86 memory model that the compiler/optimizer observes are not guaranteed by the C++ memory model.



来源:https://stackoverflow.com/questions/28111666/why-is-stdweak-ptrexpired-optimized-away

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!