2016-09-09 23 views
18

Dlaczego std::lock_guard i std::unique_lock wymagają określenia typu blokady jako parametru szablonu?Dlaczego std :: lock_guard/std :: unique_lock nie używa typu wymazania?

Rozważ następującą opcję. Po pierwsze, w detail nazw, są zajęcia typu skasowaniem (non-szablon abstrakcyjne klasy bazowej, a szablon klasa pochodna):

#include <type_traits> 
#include <mutex> 
#include <chrono> 
#include <iostream> 

namespace detail { 

    struct locker_unlocker_base { 
     virtual void lock() = 0; 
     virtual void unlock() = 0; 
    }; 

    template<class Mutex> 
    struct locker_unlocker : public locker_unlocker_base { 
     locker_unlocker(Mutex &m) : m_m{&m} {} 
     virtual void lock() { m_m->lock(); } 
     virtual void unlock() { m_m->unlock(); } 
     Mutex *m_m; 
    }; 
} 

Teraz te_lock_guard, osłona zamka typu usuwanie, wystarczy umieszczenie-news przedmiot prawidłowego typu kiedy skonstruowanej (bez dynamicznej alokacji pamięci):

class te_lock_guard { 
public: 
    template<class Mutex> 
    te_lock_guard(Mutex &m) { 
     new (&m_buf) detail::locker_unlocker<Mutex>(m); 
     reinterpret_cast<detail::locker_unlocker_base *>(&m_buf)->lock(); 
    } 
    ~te_lock_guard() { 
     reinterpret_cast<detail::locker_unlocker_base *>(&m_buf)->unlock(); 
    } 

private: 
    std::aligned_storage<sizeof(detail::locker_unlocker<std::mutex>), alignof(detail::locker_unlocker<std::mutex>)>::type m_buf; 
}; 

Sprawdziłem wydajność w porównaniu z klasami biblioteki standardowej za:

int main() { 
    constexpr std::size_t num{999999}; 
    { 
     std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); 
     for(size_t i = 0; i < num; ++i) { 
      std::mutex m; 
      te_lock_guard l(m); 
     } 
     std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now(); 
     std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl; 
    } 
    { 
     std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); 
     for(size_t i = 0; i < num; ++i) { 
      std::mutex m; 
      std::unique_lock<std::mutex> l(m); 
     } 
     std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now(); 
     std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl; 
    } 
} 

Korzystanie z g ++ z -O3, nie ma statystycznie znaczącej utraty wydajności.

+5

Co jest dla ciebie "istotne statystycznie"? Wydanie natywne wydawało się szybsze po uruchomieniu go 4-5 razy. Więc nie wiesz, co uważasz za nieistotne. I dlaczego wybrać domyślną implementację wolniejszą od obecnej? Samo pytanie jest dość interesujące. Dobry ktoś zmierzył alternatywę. – Hayt

+3

Jeśli naprawdę byłeś zbyt zdenerwowany przez konieczność ponownego wpisania nazwy typu, możesz użyć szablonu funkcji: 'auto && lock = guard_me (mx);' ([Demo] (https://ideone.com/ysxJEz) .) –

+0

@Hayt Co mam na myśli to, że statystyczny test permutacji nie odrzucił hipotez, że są z tego samego rozkładu. Według mnie skomplikowana alternatywa jest czasami szybsza, nawiasem mówiąc. Zajmując się statystykami, staram się unikać silnych stwierdzeń, takich jak "działają z tą samą szybkością". –

Odpowiedz

25

Ponieważ to komplikuje implementację bez żadnych znaczących korzyści i ukrywa fakt, że std::lock_guard i std::unique_lock są świadome rodzaju blokady, którą strzegą podczas kompilacji.

Twoje rozwiązanie jest obejściem problemu polegającym na tym, że odliczanie parametrów szablonu klasowego nie ma miejsca podczas budowy - jest to omówione w nadchodzącym standardzie.

Konieczność określenia typu zamka jest uciążliwym szablonem, który zostanie rozwiązany w C++ 17 (nie tylko dla zamków zabezpieczających) dzięki propozycji Template parameter deduction for constructors (P0091R3).

Propozycja (która została przyjęta) pozwala parametry szablonu należy wywnioskować z konstruktorów, usuwając potrzebę make_xxx(...) funkcji pomocniczych lub jawnie określić typenames że kompilator powinien być w stanie wydedukować:

// Valid C++17 
for(size_t i = 0; i < num; ++i) { 
    std::mutex m; 
    std::unique_lock l(m); 
} 
+0

To bardzo interesujące. Dzięki za link. –

+0

Uważam, że ta wersja jest lepiej sformułowana i poprawiona w niektórych miejscach (literówki itp.) Http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r2.html. Chociaż nie mogę znaleźć informacji o tym, który z nich został faktycznie zaakceptowany. – Patryk

+1

@Patryk: dziękuję, p0091r3 jest adoptowany [zgodnie z tą stroną] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/#mailing2016-07) –

7

Przetocz na C++17 ... W międzyczasie nie ma potrzeby usuwania typu. Odliczanie argumentów funkcji szablonów pozwala nam na łatwą pomoc:

template<class Mutex> 
auto make_lock(Mutex& m) 
{ 
    return std::unique_lock<Mutex>(m); 
} 

... 

std::mutex m; 
std::recursive_mutex m2; 

auto lock = make_lock(m); 
auto lock2 = make_lock(m2); 
+0

Dobra opcja , dzięki. –

+0

Tak, i jest to opcja, z której wszyscy korzystali do czasu C++ 17, w tym samego stdlib, więc jestem zaskoczony, że wcześniej tego nie widziałeś. : P –