Pregunta ¿Por qué std :: weak_ptr :: expired optimized away?


En el siguiente código, while ( !Ref.expired() ); se optimiza alegremente en un ciclo infinito. Si la línea de código se cambia a while ( !Ref.lock() );. todo funciona como se espera Entonces dos preguntas realmente:

1) ¿Cómo puede el compilador optimizar expiró cuando std::weak_ptr::expired() accede a un contador vallado de memoria?

2) Es Ref.lock() realmente seguro, o podría ser optimizado?

Muestra de código a continuación.

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

32
2018-01-23 14:05


origen


Respuestas:


Tu programa es incorrecto; las facilidades del puntero de propiedad compartida no están destinadas a utilizarse para la sincronización.

[intro.multithread]/ 24:

La implementación puede suponer que cualquier subproceso llevará a cabo una de las siguientes acciones:
  - Terminar,
  - hacer una llamada a una función de E / S de la biblioteca,
  - acceder o modificar un objeto volátil, o
  - realizar una operación de sincronización o una operación atómica.

std::weak_ptr::expired() no es una operación de sincronización o una operación atómica; todo lo que el estándar dice es que no introduce una carrera de datos. Dado que la resolución de Defecto de biblioteca 2316, std::weak_ptr::lock() se considera una operación atómica, por lo que para responder 2) su código usando Ref.lock() es válido a partir de C ++ 14.

Ahora bien, es cierto que si intentara crear su propia implementación de biblioteca de weak_ptr utilizando el idioma y las instalaciones de la biblioteca, necesariamente usaría las instalaciones de sincronización y / o operación atómica, por lo que un usuario proveería weak_ptr::expired() estaría bien dar vuelta (la implementación estaría obligada a asegurar que el hilo eventualmente progrese, [intro.multithread]/ 2 y / 25). Pero una implementación no está obligada a restringir su propia biblioteca al idioma y las instalaciones de la biblioteca.

No estoy del todo seguro de cómo el compilador está optimizando el acceso a expired(). Supongo que la biblioteca MSVC está explotando aspectos del modelo de memoria x86 que el compilador / optimizador observa no están garantizados por el modelo de memoria C ++.


8
2018-01-23 18:00