为什么std :: weak_ptr :: expired已经优化了?

Puf*_*Air 32 c++ multithreading shared-ptr visual-c++ c++11

在下面的代码中,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;
}
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 8

你的程序不正确; 共享所有权指针工具不用于同步.

[intro.multithread]/24:

实现可以假设任何线程最终将执行以下操作之一:
- 终止,
- 调用库I/O函数,
- 访问或修改易失性对象,或
- 执行同步操作或原子操作.

std::weak_ptr::expired()不是同步操作或原子操作; 标准所说的是它没有引入数据竞争.由于分辨率库缺陷2316,std::weak_ptr::lock()被认为是一个原子操作,所以回答2)你的使用代码Ref.lock()是作为C++ 14有效.

现在,如果您尝试weak_ptr使用语言和库设施创建自己的库实现,那么它必然会使用同步和/或原子操作工具,因此用户提供的weak_ptr::expired()可以旋转(根据[intro.multithread]/2和/ 25),实现将有义务确保线程最终取得进展.但是实现没有义务将自己的库限制为语言和库设施.

我不完全确定编译器如何优化访问权限expired().我猜想MSVC库正在利用编译器/优化器观察到的x86内存模型的各个方面,而不是C++内存模型所保证的.

  • 通过标题挖掘显示MSVC的stdlib使用引用计数的非原子读取来在x86上实现"weak_ptr :: expired"和"shared_ptr :: use_count".我们可以整天争论它是否严格遵守,但我认为很清楚它的QoI很差. (4认同)
  • @ecatmur在问题被添加到标准之前,是否有任何编译器实际上使`lock()`非原子*?这将是非常不直观的...... (2认同)
  • @curiousguy 嗯,也许我可以更清楚地说明这一点。下一段指出:*25 - 实现应确保原子或同步操作分配的最后一个值(按修改顺序)在有限的时间内对所有其他线程可见。* `expired` 不是值由原子或同步操作分配,并且该循环内没有其他原子读取或同步操作,因此与不应观察到更改的标准一致。 (2认同)