2013-07-25 16 views
17

Angenommen ich eine Reihe von unique_ptr haben:eine std :: unordered_set von std :: unique_ptr

std::unordered_set <std::unique_ptr <MyClass>> my_set; 

Ich bin nicht sicher, was der sichere Weg ist in der Menge zu prüfen, ob ein gegebener Zeiger vorhanden ist. Der normale Weg, es zu tun, kann sein, my_set.find() zu nennen, aber was gebe ich als Parameter?

Alles, was ich von außen habe, ist ein roher Zeiger. Also muss ich einen anderen unique_ptr vom Zeiger erstellen, ihn an find() übergeben und dann release() diesen Zeiger, sonst würde das Objekt zerstört werden (zweimal). Natürlich kann dieser Prozess in einer Funktion ausgeführt werden, so dass der Aufrufer den rohen Zeiger übergeben kann und die Konvertierungen durchführt.

Ist diese Methode sicher? Gibt es eine bessere Möglichkeit, mit einer Reihe von unique_ptr zu arbeiten?

+0

Dank. Ich muss nichts bewegen oder kopieren, also ist unique_ptr in Ordnung. Ich muss nur den Anrufer einen rohen Zeiger geben lassen, und ich muss überprüfen, ob ein passender unique_ptr in der Menge vorhanden ist. – cfa45ca55111016ee9269f0a52e771

+1

'unique_ptr' ist offensichtlich nicht das, was Sie brauchen, da Sie eindeutig andere Zeiger auf das Objekt haben. –

+2

Der Besitzer des unique_ptr ist der einzige Besitzer des Speichers, und alle anderen enthalten nur Referenzen. Ich könnte shared :: ptr überall im owner und weak_ptr verwenden, aber dann wird jedes Objekt von einem einzigen shared_ptr referenziert. Ich brauche das Teilen nicht, nur einen einzigen Besitzer – cfa45ca55111016ee9269f0a52e771

Antwort

17

Sie können auch einen Deleter verwenden, der optional nichts tut.

template<class T> 
struct maybe_deleter{ 
    bool _delete; 
    explicit maybe_deleter(bool doit = true) : _delete(doit){} 

    void operator()(T* p) const{ 
    if(_delete) delete p; 
    } 
}; 

template<class T> 
using set_unique_ptr = std::unique_ptr<T, maybe_deleter<T>>; 

template<class T> 
set_unique_ptr<T> make_find_ptr(T* raw){ 
    return set_unique_ptr<T>(raw, maybe_deleter<T>(false)); 
} 

// ... 

int* raw = new int(42); 
std::unordered_set<set_unique_ptr<int>> myset; 
myset.insert(set_unique_ptr<int>(raw)); 

auto it = myset.find(make_find_ptr(raw)); 

Live example.

+0

+1 für Eleganz – sehe

+0

Ich wäre versucht, einen [impliziten "Konstruktor zu betrachten Hier können Sie stattdessen 'set_unique_ptr (roh, falsch)'] (http://coliru.stacked-crooked.com/view?id=69a8470528f5380f02eba643929357e7-e54ee7a04e4b807da0930236d4cc94dc) stattdessen. Gedanken? (Ich weiß, ich weiß. Implizite Konversionen, _evil_. Aber, konkret, irgendein Haken für _this particular_ use case?) – sehe

8

Sie können einen std::map<MyClass*, std::unique_ptr<MyClass>> anstelle eines Satzes verwenden. Dann können Sie Elemente wie folgt hinzu:

std::unique_ptr<MyClass> instance(new MyClass); 
map.emplace(instance.get(), std::move(instance)); 
+0

Gute Idee! Aber es macht das Set doppelt so groß. In meinem Fall ist das kein Problem, aber ich frage mich nur - wird die beschriebene Methode funktionieren und vollkommen sicher sein? (übrigens sollte es unordered_map, nicht map) sein – cfa45ca55111016ee9269f0a52e771

+3

-1. Was ist falsch an 'std :: set'? In diesem Fall ist 'std :: map' schlecht. Schlüssel und Wert sind im Wesentlichen das gleiche Objekt! – Nawaz

+1

@Nawaz das unique_ptr enthält den Speicher, während der rohe Zeiger für find() und alle anderen Methoden verwendet wird, die nach Schlüsseln suchen. – cfa45ca55111016ee9269f0a52e771

10

Beachten Sie, dass die Fähigkeit, heterogenes Lookups auf Standard-Container ist Gegenstand einiger Vorschläge zu tun.

http://cplusplus.github.io/LWG/lwg-proposal-status.html Listen

  • N3465 heterogenen Vergleich Lookup assoziativen Behälter für TR2 (Rev 2) Hinzufügen [mit N3573 handle]
  • N2882 id.
  • N3573 Heterogene Erweiterungen ungeordneten Behälter [Griff mit N3465]

Besonders die letztere sieht aus wie es Ihren Anwendungsfall abdecken würde.

Vorerst ist hier ein IMO nicht sehr hübsch, aber arbeiten alternative Lösung (O (n)):

#include <iterator> 
#include <iostream> 
#include <algorithm> 

#include <unordered_set> 
#include <memory> 

#include <cassert> 

struct MyClass {}; 

template <typename T> 
struct RawEqualTo 
{ 
    RawEqualTo(T const* raw) : raw(raw) {} 

    bool operator()(T const* p) const 
     { return raw == p; } 
    bool operator()(std::unique_ptr<T> const& up) const 
     { return raw == up.get(); } 

    private: 
    T const* raw; 
}; 


using namespace std; 
int main() 
{ 
    std::unordered_set <std::unique_ptr <MyClass>> my_set; 

    my_set.insert(std::unique_ptr<MyClass>(new MyClass)); 
    my_set.insert(std::unique_ptr<MyClass>(new MyClass)); 

    auto raw = my_set.begin()->get(); 

    bool found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw)); 
    assert(found); 

    raw = new MyClass; 

    found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw)); 
    assert(!found); 

    delete raw; 
} 

Warnung Es ist auch sehr ineffizient, natürlich.

+0

+1 Für die Erwähnung heterogener Lookups (und zumindest eine Warnung, dass 'std :: find_if 'treibt die Verwendung einer' std :: unordered_set' ad absurdum). –

+0

Heterogene Erweiterungen lösen das Problem, sie tun * genau * was ich brauche :-) Aber die Lösung, die Sie hier vorschlagen, ist in der Tat zu ineffizient. Ich brauche den O (1) Durchschnitt von unordered_set :: find – cfa45ca55111016ee9269f0a52e771

+0

@Nawaz Weil ein 'std :: shared_ptr' wäre absolut fehlplatziert, wenn der Speicher gerade nicht geteilt wird. –

4

Wenn das Ziel ist konstante Zeit für das Nachschlagen, ich glaube nicht, dass gibt es eine Lösung. std::unordered_set<std::unique_ptr<MyClass>>::find erfordert ein std::unique_ptr<MyClass> als Argument. Sie müssen entweder den Container ändern oder den enthaltenen Typ ändern.

Eine Möglichkeit könnte sein, ersetzen std::unique_ptr mit std::shared_ptr, und dem Rest des so-Code ändern, dass alle MyClass in eine shared_ptr setzen, sobald sie erstellt werden, und werden nur durch gemeinsamen Zeiger manipulieren. Logischerweise das ist wahrscheinlich sowieso kohärenter: unique_ptr ziemlich viel impliziert (durch seinen Namen, sowie seine Semantik), dass dort sind keine anderen Zeiger auf das Objekt. Auf der anderen Seite können Sie Shared_ptr nicht verwenden, wenn z. MyClass hat Zeiger auf andere MyClass, die einen Zyklus aufbauen können.

Andernfalls, wenn Sie O (lg n) Zugang annehmen können, anstatt ständigen Zugang (die Differenz in der Regel nicht bemerkbar macht werden, bis die Tische ziemlich groß sind), können Sie ein std::vector<MyClass>, mit std::lower_bound verwenden behalte es sortiert. Im Gegensatz zu std::unordered_set<>::find, std::lower_bound bedeutet nicht erfordern, dass der Zielwert denselben Typ hat wie der value_type der Sequenz; alles, was Sie tun müssen, ist , um sicherzustellen, dass sie vergleichbar sind, etwa durch ein Compare Objekt entlang der Linien der Bereitstellung:

class MyClassPtrCompare 
{ 
    std::less<MyClass const*> cmp; 
public: 
    bool operator()(std::unique_ptr<MyClass> const& lhs, 
        std::unique_ptr<MyClass> const& rhs) const 
    { 
     return cmp(lhs.get(), rhs.get()); 
    } 
    bool operator()(MyClass const* lhs, 
        std::unique_ptr<MyClass> const& rhs) const 
    { 
     return cmp(lhs, rhs.get()); 
    } 
    bool operator()(std::unique_ptr<MyClass> const& lhs, 
        MyClass const* rhs) const 
    { 
     return cmp(lhs.get(), rhs); 
    } 
    bool operator()(MyClass const* lhs, 
        MyClass const* rhs) const 
    { 
     return cmp(lhs, rhs); 
    } 
}; 

Insertion eine Anzahl von Bewegungen beinhalten kann, aber ein std::unique_ptr sollte bewegen ziemlich billig sein und die verbesserte Lokalität dieser Lösung könnte die zusätzlichen Laufzeitkosten, die ansonsten auferlegt werden, kompensieren.

+0

+1 Scheint wie ein guter Kompromiss. Seltsamerweise hat keiner der 'find_if'-Befürworter das gemacht (aber Ok, habe selbst auch nicht daran gedacht). –

+0

Gute Idee. Momentan stört mich O (lg n) nicht, aber ich plane, eine große Anzahl von Objekten und viele Anfragen zu unterstützen, daher bevorzuge ich einen durchschnittlichen O (1) -Ansatz, wenn möglich – cfa45ca55111016ee9269f0a52e771

+2

@ fr33domlover Aber Sie könnten überrascht sein, wie gut das ist sortierter Vektor wird sich in der Praxis verhalten, schätze ich. –

Verwandte Themen