2015-01-23 19 views
32

W poniższym kodzie while (!Ref.expired()); jest z radością zoptymalizowany w nieskończoną pętlę. Jeśli linia kodu zostanie zmieniona na while (!Ref.lock());. wszystko działa zgodnie z oczekiwaniami. Tak więc dwa pytania naprawdę:Dlaczego std :: weak_ptr :: expired zoptymalizowano?

1) W jaki sposób kompilator optymalizuje czas wygasania, gdy std::weak_ptr::expired() uzyskuje dostęp do zabezpieczonego przed zapisem licznika?

2) Czy w rzeczywistości jest to bezpieczne, czy też można je zoptymalizować?

Przykładowy kod poniżej.

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

przegłosowano na ** z radością ** * zoptymalizowano z dala *. sugerujesz, że maszyny i/lub programy mogą się dobrze bawić? – Walter

+14

@Walter Miałem wyraźne wrażenie, że kompilator był bardzo zadowolony z siebie. Ja jednak byłam bardzo _displeased_. Kompilator i ja mamy burzliwy związek. –

+0

[Wydaje się pracować z gcc] (http://coliru.stacked-crooked.com/a/36ffe423f76ecf02). [Działa również z klangiem] (http://coliru.stacked-crooked.com/a/5b3827c261b54b90). Prawdopodobnie błąd w studiu visual. – interjay

Odpowiedz

8

Twój program jest niepoprawny; współrzędne wskaźnika własności nie są przeznaczone do synchronizacji.

[intro.multithread]/24:

Implementacja może zakładać, że każdy wątek będzie w końcu zrobić jedną z następujących czynności:
- zakończyć,
- zadzwonić do biblioteki I/O,
- dostęp lub modyfikacja obiektu lotnego lub
- wykonanie operacji synchronizacji lub operacji atomowej.

std::weak_ptr::expired() nie jest operacją synchronizacji ani operacją atomową; Standard mówi, że nie wprowadza wyścigu danych. Ponieważ rozdzielczość do Library defect 2316, std::weak_ptr::lock() jest uważana za operację atomową, więc aby odpowiedzieć 2) twój kod przy użyciu Ref.lock() jest ważny od C++ 14.

Prawdą jest, że jeśli spróbujesz stworzyć własną bibliotekę o implementacji weak_ptr przy użyciu języka i bibliotek, konieczne będzie użycie funkcji synchronizacji i/lub obsługi operacyjnej, więc użytkownik pod warunkiem weak_ptr::expired() będzie OK, aby się zakręcić (implementacja byłaby zobowiązana do zapewnienia, że ​​wątek ostatecznie osiągnął postęp, za [intro.multithread]/2 i/25). Jednak implementacja nie jest zobowiązana do ograniczenia własnej biblioteki do języka i bibliotek.

Nie jestem do końca pewien, w jaki sposób kompilator optymalizuje dostęp do expired(). Przypuszczam, że biblioteka MSVC wykorzystuje aspekty modelu pamięci x86, którego przestrzega kompilator/optymalizator, nie jest gwarantowany przez model pamięci C++.

+1

'Nie jest std :: weak_ptr :: lock() operacja synchronizacji lub operacja atomowa': nie jest to w konflikcie z [util.smartptr.weak.obs]/5, które (przynajmniej w N4296) jawnie mówi ', wykonany atomowo'? –

+0

@AndyProwl ah, więc problem 2316 dostał się do C++ 14? (Tak, zgodnie z Biblioteką R87.) Zgadzam się, że to zmienia rzeczy; Zmienię moją odpowiedź. – ecatmur

+2

@ecatmur Czy któryś kompilator faktycznie zrobił 'lock()' nonatomic * przed * tym problemem został dodany do standardu? To byłoby niesamowicie nieintuicyjne ... – Barry

Powiązane problemy