2014-07-14 11 views
8

Hier ist ein Problem, auf das ich oft mit RAII stoße. Ich habe mich gefragt, ob jemand eine gute Lösung dafür hatte.RAII und abgeleitete Template-Argumente

Beginnen Sie mit Ihrem Standard-RAH Utility-Klasse:

class RAIIHelper { 
    RAIIHelper() { 
    AcquireAResource(); 
    } 
    ~RAIIHelper() { 
    ReleaseTheResource(); 
    } 
}; 

nun aus verschiedenen Gründen, ich brauche es eine Vorlage zu machen. Nehmen wir an, auch den Konstruktor ein Argument der Template-Parameter-Typ nimmt:

template <typename T> 
class RAIIHelper { 
    RAIIHelper(T arg) { 
    AcquireAResource(); 
    } 
    ~RAIIHelper() { 
    ReleaseTheResource(); 
    } 
}; 

Betrachten wir nun eine Verwendung Website:

void func() { 
    RAIIHelper<SomeType> helper(someObj); 
} 

Es ist ärgerlich, haben zu schreiben, SomeType wann kann es aus someObj abgeleitet werden, so schreibe ich eine Hilfsfunktion den Typ ableiten:

template <typename T> 
RAIIHelper<T> makeRAIIHelper(T arg) { 
    return RAIIHelper<T>(arg); 
} 

Jetzt kann ich es verwenden, etwa so:

void func() { 
    auto helper = makeRAIIHelper(someObj); 
} 

Wunderbar, oder? Außer dass es einen Haken gibt: RAIIHelper muss jetzt kopierbar oder beweglich sein, und der Destruktor - der die Ressource freigibt - kann möglicherweise zweimal aufgerufen werden: einmal für das temporäre von makeRAIIHelper zurückgegebene und einmal für die lokale Variable in der aufrufenden Funktion.

In der Praxis führt mein Compiler RVO und der Destruktor wird nur einmal aufgerufen. Dies ist jedoch nicht garantiert. Dies kann von der Tatsache gesehen werden, wenn ich versuche, RAIIHelper einen = delete 'd move-Konstruktor zu geben, kompiliert der Code nicht mehr.

Ich könnte RAIIHelper einen zusätzlichen Status hinzufügen, so dass es ReleaseTheResource() nicht zu rufen, nachdem es verschoben wurde, aber das ist zusätzliche Arbeit, die unnötig war, bevor ich makeRAIIHelper() hinzugefügt, um den Typabzug zu erhalten.

Gibt es eine Möglichkeit, den Typ Abzug zu bekommen, ohne extra Staat zu RAIIHelper hinzufügen?

+0

Abgesehen davon, dass du 'unique_ptr' dort neu erfindest, bist du sicher, dass es absolut kein natürliches Sentinel-Objekt vom Typ' SomeType' gibt? – Deduplicator

+0

Sie können ein 'unique_ptr' verwenden. –

+0

Schreiben Sie einen richtigen Move-Konstruktor und deaktivieren Sie den Kopierkonstruktor. Dies sollte kein Problem sein. –

Antwort

8

Es gibt eine sehr einfache Lösung: Verwenden Sie einen Verweis auf das temporäre Objekt, anstatt es in eine lokale Variable zu kopieren.

void func() 
{ 
    auto&& helper = makeRAIIHelper(someObj); 
} 
+5

Und wenn Sie 'return RAIIHelper (arg);' mit "direct-Initialisierung" à la 'return {arg}' ersetzen, muss der Typ nicht beweglich sein. – dyp

+0

Wie wird das in der nächsten Zeile funktionieren, wenn das temporäre Objekt den Gültigkeitsbereich verlässt? –

+4

@ StianV.Svedenborg Die Lebensdauer dieses temporären Objekts wird bis zum Ende des Bereichs erweitert. – dyp

1

Aufbauend auf den bisherigen Antworten und Kommentare:

Sie könnten die Verantwortung beweglich verlassen unique_ptr, und senden Sie Ihre Ressource wie folgt aus:

template <class T> 
auto makeRAII(T arg) -> std::unique_ptr<RAIIHelper> { 
    return make_unique(RAIIHelper<T>(arg)); 
} 

Jetzt Tive es wie eine statische variabel, aber möglicherweise nicht kopierbar & unbeweglich.

+0

Das funktioniert, aber es verursacht den Overhead einer unnötigen dynamischen Zuweisung. – HighCommander4

+0

Aye, das kann oder kann es nicht wert sein, abhängig von der Lebensdauer des Objekts und der Faulheit (was eine gute Sache ist) des Entwicklers. –