Подтвердить что ты не робот

Почему std:: weak_ptr:: expired оптимизирован?

В следующем коде while ( !Ref.expired() ); радостно оптимизируется в бесконечный цикл. Если строка кода изменена на while ( !Ref.lock() );. все работает так, как ожидалось. Итак, на самом деле два вопроса:

1) Как можно оптимизировать компилятор, если std::weak_ptr::expired() обращается к счетчику с памятью?

2) Действительно ли Ref.lock() безопасен, или это тоже можно оптимизировать?

Пример кода ниже.

#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;
}
4b9b3361

Ответ 1

Ваша программа неверна; объекты указателя совместного использования не предназначены для синхронизации.

[intro.multithread]/24:

Реализация может предполагать, что любой поток в конечном итоге сделает одно из следующего:
- завершение,
- сделать вызов функции ввода-вывода библиотеки,
- доступ или изменение изменчивого объекта или
- выполнить операцию синхронизации или атомную операцию.

std::weak_ptr::expired() не является операцией синхронизации или атомной операцией; все Стандарт говорит, что он не вводит гонку данных. Поскольку разрешение дефект библиотеки 2316, std::weak_ptr::lock() считается атомной операцией, поэтому для ответа 2) ваш код с использованием Ref.lock() действителен как из С++ 14.

Теперь, правда, если бы вы попытались создать свою собственную библиотечную реализацию weak_ptr, используя средства языка и библиотеки, она обязательно использовала бы средства синхронизации и/или атомной операции, поэтому предоставленный пользователем weak_ptr::expired() было бы в порядке, чтобы включить (реализация должна была гарантировать, что поток в конечном итоге достигнет прогресса, за [intro.multithread]/2 и /25). Но реализация не обязана ограничивать свою библиотеку языковыми и библиотечными средствами.

Я не совсем уверен, как компилятор оптимизирует доступ к expired(). Я предполагаю, что библиотека MSVC использует аспекты модели памяти x86, которые наблюдатели компилятора/оптимизатора не гарантированы моделью памяти С++.