2016-09-09 14 views
18

std::lock_guardstd::unique_lockはなぜテンプレートタイプとしてロックタイプを指定する必要がありますか?std :: lock_guard/std :: unique_lockが型消去を使用しないのはなぜですか?

次の代替方法を検討してください。

#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、型消去ロックガード、単に配置-ニュースオブジェクト:まず、detail名前空間で、型消去クラス(非テンプレート抽象基底クラス、およびテンプレートの派生クラス)があります私は標準ライブラリのクラス対パフォーマンスをチェックしました

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; 
    } 
} 
(動的なメモリ割り当てなし)を構築し、正しいタイプの

-O3でg ++を使用すると、統計的に有意なパフォーマンスの低下はありません。

+5

あなたのために '統計的に有意義なものは何ですか?ネイティブの実装は、それを4〜5回実行すると高速に見えました。あなたが見ているものが重要でないと分かりません。そしてなぜ現在の選択肢よりも遅いデフォルトの実装を選択するのですか?しかし、質問自体はかなりinteresstingです。良い人が代替を測定しました。 – Hayt

+3

実際にタイプ名をもう一度入力しなくてはいけない場合は、関数テンプレートを使用することができます: 'auto && lock = guard_me(mx);'([Demo](https://ideone.com/ysxJEz) 。) –

+0

@Hayt私が意味したのは、統計的置換検査は、彼らが同じ分布から来たという仮説を棄却しなかったということです。私にとって、複雑な代替案は実際には偶然にも時にはより高速になることがあります。統計を扱うと、「彼らは同じスピードで走っている」などの強い声明を避けようとしています。 –

答えて

25

このため、実装には大きな利点はないため、std::lock_guardstd::unique_lockはコンパイル時に保護しているロックの種類を認識していません。

あなたのソリューションは、クラステンプレートのパラメータ控除が建設中に行われないという事実の回避策です。これは、今後の標準で取り上げられています。ロックタイプを指定する必要

で解決される迷惑な決まり文句であるC++ 17 (だけでなく、ロックガード用)Template parameter deduction for constructors (P0091R3)提案へ感謝。

(承認された)提案は、make_xxx(...)ヘルパー関数の必要性を取り除く、テンプレートパラメータは、コンストラクタから推定することを可能にするか、明示的にコンパイラが推測することができるはず型名を指定します。

// 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

これはstdlib自体を含め、誰もがC++ 17まで使用していたオプションなので、以前は見たことがないのに驚いています。 :P –

関連する問題