2016-09-09 2 views
18

Почему std::lock_guard и std::unique_lock необходимо указать тип блокировки в качестве параметра шаблона?Почему std :: lock_guard/std :: unique_lock использовать стирание типа?

Рассмотрите следующий вариант. Во-первых, в detail пространстве имен, существуют классы типа стирания (нешаблонном абстрактный базовый класс и шаблон производный класс):

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

Теперь te_lock_guard, тип стиранием замок охранник, просто места размещения новостей объект правильного типа при сконструированной (без динамического выделения памяти):

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

Я проверил производительность по сравнению с классами из стандартной библиотеки:

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

Используя g ++ с -O3, статистически значимых потерь производительности нет.

+5

что такое 'статистически значимое' для вас? Нативная реализация показалась быстрее после запуска 4-5 раз. Поэтому не уверен, что вы считаете незначительным. И почему выбрать реализацию по умолчанию, которая медленнее текущей альтернативы? Однако сам вопрос довольно интересен. Хороший кто-то измерил альтернативу. – Hayt

+3

Если вы действительно были слишком расстроены повторением ввода типа, вы можете использовать шаблон функции: 'auto && lock = guard_me (mx);' ([Demo] (https://ideone.com/ysxJEz) .) –

+0

@Hayt Я имел в виду, что статистический тест перестановки не отвергал гипотезы, что они принадлежат к одному и тому же распределению. Между прочим, сложная альтернатива иногда бывает быстрее, кстати. В статистике я стараюсь избегать сильных утверждений, таких как «они работают с одинаковой скоростью». –

ответ

25

Поскольку это усложняет реализацию без каких-либо существенных преимуществ и скрывает тот факт, что std::lock_guard и std::unique_lock знают о типе блокировки, которую они охраняют во время компиляции.

Ваше решение является обходным путем для того, что вычитание параметра шаблона шаблона не происходит во время строительства - это рассматривается в предстоящем стандарте.

, потребовавший, чтобы определить тип блокировки раздражает шаблонный, которая будет решена в C++ 17 (не только для блокировки охранников) благодаря к Template parameter deduction for constructors (P0091R3) предложению.

Предложение (которое было принято), позволяет установить параметры шаблона должны быть выведены из конструкторов, устраняя необходимость для make_xxx(...) вспомогательных функций или явно указать typenames, что компилятор должен быть в состоянии вывести:

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

Это очень интересно. Спасибо за ссылку. –

+0

Я считаю, что эта редакция лучше сформулирована и исправлена ​​в некоторых местах (опечатки и т. Д.) Http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r2.html. Хотя я не могу найти информацию о том, какой из них был фактически принят. – Patryk

+1

@Patryk: спасибо, p0091r3 принят [согласно этой странице] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/#mailing2016-07) –

7

Roll on C++17 ... В то же время нет необходимости в стирании типа. Вычисление аргумента функции шаблона позволяет нам быть легким помощником:

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

Хороший вариант , благодаря. –

+0

Да, и это вариант, который все использовали до C++ 17, включая сам stdlib, поэтому я удивлен, что вы его раньше не видели. :П –

Смежные вопросы