2015-06-30 6 views
6

Ich habe eine Hybrid-lock-Klasse, die Spin versucht eine Sperre für eine (Kompilierzeit fest) Anzahl von Spins vor dem Zurückfallen auf eine std::mutex bis die Sperre verfügbar wird.So verwenden Sie eine andere Klasse als Klassenvorlage Spezialisierung

Simplified:

#include <mutex> 

template<unsigned SPIN_LIMIT> 
class hybrid_lock { 
public: 
    void lock(){ 
     for(unsigned i(0);i<SPIN_LIMIT;++i){ 
      if(this->mMutex.try_lock()){ 
       return;   
      } 
     } 
     this->mMutex.lock(); 
    } 
    void unlock(){ 
     this->mMutex.unlock(); 
    } 
private: 
    std::mutex mMutex; 
}; 

Im speziellen Fall von SPIN_LIMIT==0 diese zurückfällt a 'Plain' std::mutex zu sein (d.h. keine sichtbaren Spins).

Also habe ich spezialisiert, dass auf:

template<> 
class hybrid_lock<0> : public std::mutex {}; 

Es funktioniert gut, aber ist, dass die genehmigte Art und Weise Klassenvorlagen von spezialisierten anderen (vorbestehenden) Vorlage zu sein?

Antwort

2

Hinweis: Ich beantwortete die eigentliche Frage eher als die im Titel.

Nun, jetzt hybird_lock<0> und hybird_lock<1> sind etwas ganz anderes, man leitet von std::mutex und das andere enthält/Wraps es. Dies ändert den gesamten Aufbau von hybird_lock und die Bedeutung dahinter. I.e. Sie sind nicht semantisch gleich. Dies könnte zu einigen unerwarteten Konsequenzen führen - hybird_lock<0> würde eine ganze Menge Zeug erben, was andere Fälle nicht hätten.

Wenn das der einzige Unterschied ist würde ich mich überhaupt nicht mit Spezialisierung beschäftigen. Denken Sie daran, dass die Null-Fälle zur Kompilierungszeit bekannt sein werden, und so sicher wie es geht, wird die gesamte Schleife vollständig optimiert.

Wenn es andere wichtig waren (oder tatsächlich) Optimierungen, würde ich für etwas gehen würde wie:

template<> 
class hybrid_lock<0> { 
public: 
    void lock(){ 
     this->mMutex.lock(); 
    } 
    void unlock(){ 
     this->mMutex.unlock(); 
    } 
private: 
    std::mutex mMutex; 
}; 

Diese Implementierung macht 0 einen Sonderfall dar, sondern als etwas, fast völlig anders.

+0

Ich stimme dem Punkt zu. Zum Beispiel könnten Sie eine Instanz von 'hybrid_lock <0> 'an Funktionen übergeben, die einen Verweis auf' std :: mutex' erwarten, aber das gleiche gilt nicht für 'hybrid_lock ' alle anderen 'n'. Das könnte dazu führen, dass Programme durch Abstimmen "brechen", was nie ein schöner Anblick ist.NB: Die reale hybride Klasse ist komplizierter als die gezeigte und ein Compiler kann die Vereinfachung nicht erwarten. Vielleicht ist das die Antwort "verwende keine triviale Eindämmung und Weiterleitung". – Persixty

+0

@DanAllen Das war genau mein Punkt, es ist mehr dran, wie Verhalten auf der Kopie, Zerstörung, etc. Vielleicht für 'std :: mutex' würde es keine Nebenwirkungen geben, aber als ein allgemeines Muster, trotz wie viel Versuchung dies schaut, es ist wahrscheinlich keine gute Idee =). Ich würde mich für eine Neuimplementierung entscheiden, oder, falls nötig, eine etwas ausgefeiltere Methode, wie die Antwort von Roger nahelegt. – luk32

1

Es gibt keine "offizielle" Möglichkeit dies zu tun, aber hier ist ein guter Weg - mit Vorlagen ist es oft eine bessere Idee, die Hauptvorlagenklasse in kleinere 'action' oder 'function' Vorlagenklassen zu zerlegen. Auf diese Weise können mehr Kontrolle und Granularität über Spezialisierung zu erhalten, was bedeutet, dass Sie nur die Hauptlogik an einem Ort zu halten haben:

#include <iostream> 
#include <mutex> 

// general form of the spin_locker 
template<unsigned SPIN_LIMIT, class Mutex> 
struct spinner 
{ 
    static void lock(Mutex& m) { 
     for (unsigned i = 0 ; i < SPIN_LIMIT ; ++i) 
      if (m.try_lock()) 
       return; 
     m.lock(); 
    } 
}; 

// optmised partial specialisation for zero spins 
template<class Mutex> 
struct spinner<0, Mutex> 
{ 
    static void lock(Mutex& m) { 
     m.lock(); 
    } 

}; 

template<unsigned SPIN_LIMIT, class Mutex = std::mutex> 
class hybrid_lock { 

    using spinner_type = spinner<SPIN_LIMIT, Mutex>; 

public: 
    void lock(){ 
     spinner_type::lock(mMutex); 
    } 

    void unlock(){ 
     mMutex.unlock(); 
    } 

    std::unique_lock<Mutex> make_lock() { 
     return std::unique_lock<Mutex>(mMutex); 
    } 

private: 
    Mutex mMutex; 
}; 

// since only the 'spinner' functor object needs specialising there is now no need to specialise the main logic 

using namespace std; 

auto main() -> int 
{ 
    hybrid_lock<100> m1; 
    hybrid_lock<0> m2; 
    hybrid_lock<100, std::recursive_mutex> m3; 
    hybrid_lock<0, std::recursive_mutex> m4; 

    auto l1 = m1.make_lock(); 
    auto l2 = m2.make_lock(); 
    auto l3 = m3.make_lock(); 
    auto l4 = m4.make_lock(); 

    return 0; 
} 
+0

Ich mag das. Guter Punkt, dass ich meine Mutex-Klasse parametrisieren sollte. Meine Klasse arbeitet nur mit dem Bog-Standard 'std :: mutex'. Sie könnten mit 'recursive_mutex' (und Freunden) verwendet werden. Ich mag auch die partielle Spezialisierung "Slot-in". Der wirkliche Fall ist tatsächlich komplizierter und würde mit einigen redundanten Elementvariablen enden. Obwohl das nicht unbedingt ein Problem ist. – Persixty

+0

@DanAllen können Sie diese redundanten Elementvariablen weiter eliminieren, indem Sie Ihre Klasse in immer kleinere logische Komponenten zerlegen. Die allgemeine Form dieser kleineren logischen Komponenten würde die Elementvariablen enthalten, und die spezialisierten Versionen von ihnen möglicherweise nicht. Dieser Ansatz bietet eine Reihe von Vorteilen: leichtere Komponententests, bessere Logikkapselung und perfekte Effizienz. –

1

Richard Hodges Antwort ist groß, aber Sie können einfach -Methodenüberladung lock:

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

template<class Mutex = std::mutex> 
struct hybrid_lock { 
    template<int N> 
    void lock(std::integral_constant<int, N> val){ 
     for (unsigned i = 0 ; i < val() ; ++i) 
      if (mMutex.try_lock()) 
       return; 
     mMutex.lock(); 
    } 

    void lock(std::integral_constant<int, 0>){ 
     mMutex.lock(); 
    } 

    void unlock(){ 
     mMutex.unlock(); 
    } 

    std::unique_lock<Mutex> make_lock() { 
     return std::unique_lock<Mutex>(mMutex); 
    } 

private: 
    Mutex mMutex; 
}; 

template <int N> 
constexpr 
std::integral_constant<int, N> IC; 

int main() { 

    hybrid_lock<> m1; 
    hybrid_lock<> m2; 

    m1.lock(IC<0>); 
    m2.lock(IC<100>); 
    return 0; 
} 

IC ist variable template, von C++ 14, wenn Ihr Compiler nicht unterstützt Sie Typ alias type alias stattdessen verwenden können:

template<int N> 
using IC = std::integral_constant<int, N>; 

und verwenden Sie es so

m.lock(IC<0>{}); 
+0

Ich mag das auch sehr. Ich mag die Tatsache, dass es das 'Spin-Limit' für den Aufruf zum Sperren (N) parametrisiert. Ich habe ein "Lehrbuch" -Produzent/Verbrauchermodell, und ich kann mir vorstellen, dass es auf beiden Seiten des Zauns unterschiedliche Schleudergrenzen geben könnte. Warum ist das besser als Vorlage void lock(); ? Was ist 'std :: integral_constant ' hinzufügen? – Persixty

+0

Sie können die Vorlagenmethode für '0' überschreiben. Sie können versuchen, 'template ' zu verwenden und Sperre für N == 0 zu spezialisieren, aber da hybrid_lock auch Schablone ist, kann es nicht kompilieren. – p2rkw

Verwandte Themen