2009-09-06 6 views
6

Ich versuche, eine „spärliche“ Vektor-Klasse in C++ zu erstellen, etwa so:Overloading operator [] für einen spärlichen Vektor

template<typename V, V Default> 
class SparseVector { 
    ... 
} 

Intern wird es durch ein std::map<int, V> dargestellt werden (wobei V das ist Art des gespeicherten Wertes). Wenn ein Element in der Map nicht vorhanden ist, werden wir so tun, als sei es gleich dem Wert Default aus dem Template-Argument.

Ich habe jedoch Probleme beim Überladen des tiefgestellten Operators, []. Ich muss den []-Operator überladen, da ich Objekte aus dieser Klasse in eine Boost-Funktion übergebe, die erwartet, dass [] ordnungsgemäß funktioniert.

Die Version const ist einfach genug: Überprüfen Sie, ob der Index in der Karte ist, geben Sie den Wert zurück, falls dies der Fall ist, oder andernfalls Default.

Allerdings erfordert die nicht-const Version, dass ich eine Referenz zurückgebe, und das ist, wo ich in Schwierigkeiten komme. Wenn der Wert nur lautet, lesen Sie, ich brauche (noch will) nichts zur Karte hinzufügen; aber wenn es geschrieben ist, muss ich möglicherweise einen neuen Eintrag in die Karte einfügen. Das Problem ist, dass der überladene [] nicht weiß, ob ein Wert gelesen oder geschrieben ist. Es gibt nur eine Referenz zurück.

Gibt es eine Möglichkeit, dieses Problem zu lösen? Oder vielleicht um es zu umgehen?

+2

boost :: mapped_vector <> sollte etwas ähnliches tun - Sie können es für Ideen studieren (oder vielleicht einfach verwenden). –

+0

Es unterstützt meine Standardwerte nicht, und ich wollte dies auch für eine zweidimensionale Matrix tun, so dass es direkt nicht in Frage kommt. Aber immer noch eine nützliche Referenz! – Thomas

Antwort

13

Es kann einige sehr einfache Trick sein, aber ansonsten denke ich operator[] nur etwas zurückgeben muss, die von V zugeordnet werden können (und V umgewandelt), nicht unbedingt ein V &. Ich denke, Sie müssen ein Objekt mit einer überladenen operator=(const V&) zurückgeben, die den Eintrag in Ihrem Sparse-Container erstellt.

Sie müssen jedoch überprüfen, was die Boost-Funktion mit ihrem Template-Parameter bewirkt - eine benutzerdefinierte Konvertierung in V beeinflusst, welche Conversion-Ketten möglich sind, zum Beispiel, indem verhindert wird, dass weitere benutzerdefinierte Conversions in derselben vorkommen Kette.

+0

Dies ist die einzige Lösung. Auf der anderen Seite sind Proxy-Objekte auch nicht perfekt - wenn beispielsweise "V" überladene Konvertierungsoperatoren hatte, kann ein Proxy diese nicht nahtlos propagieren. –

+0

Ah habe gerade die Boost-Vektoren getestet. Sieht so aus, als ob sie sich auch so verhalten. Interessant. –

+0

(Und 'vector ' ist auch kein STL-Container.) – sbi

9

Lassen Sie nicht den nichtkonstanten Operator & Implementierung eine Referenz, sondern ein Proxy-Objekt zurückgeben. Sie können dann den Zuweisungsoperator des Proxy-Objekts implementieren, um Lesezugriffe auf Operator [] von Schreibzugriffen zu unterscheiden.

Hier ist ein Code-Skizze, um die Idee zu veranschaulichen. Dieser Ansatz ist nicht schön, aber gut - das ist C++. C++ Programmierer verschwenden keine Zeit mit Schönheitswettbewerben (sie hätten auch keine Chance). ;-)

template <typename V, V Default> 
ProxyObject SparseVector::operator[](int i) { 
    // At this point, we don't know whether operator[] was called, so we return 
    // a proxy object and defer the decision until later 
    return ProxyObject<V, Default>(this, i); 
} 

template <typename V, V Default> 
class ProxyObject { 
    ProxyObject(SparseVector<V, Default> *v, int idx); 
    ProxyObject<V, Default> &operator=(const V &v) { 
     // If we get here, we know that operator[] was called to perform a write access, 
     // so we can insert an item in the vector if needed 
    } 

    operator V() { 
     // If we get here, we know that operator[] was called to perform a read access, 
     // so we can simply return the existing object 
    } 
}; 
+0

Gibt es eine Möglichkeit, einen Typ in z.B. sowohl ein 'const int &' und ein 'int &', und lässt der Compiler den ersteren auswählen, wenn er einen nicht schreibbaren Lvalue verwenden kann? Ich möchte, dass ein Proxy-Objekt einen Verweis auf ein Feld zurückgibt, das vom Hauptobjekt geladen wird, und dann muss sein Destruktor das Feld zurück zum Hauptobjekt kopieren, wenn es sich geändert hat, aber ich kann nicht herausfinden, wie um das Zurückschreiben bei einem Nur-Lese-Zugriff zu vermeiden. – supercat

1

Ich frage mich, ob dieses Design solide ist.

Wenn Sie eine Referenz zurückgeben möchten, bedeutet dies, dass Clients der Klasse das Ergebnis des Aufrufs operator[] in einer Referenz speichern und zu einem späteren Zeitpunkt von/lesen/lesen können. Wenn Sie eine Referenz nicht zurückgeben und/oder nicht jedes Mal ein Element einfügen, wenn ein bestimmter Index adressiert wird, wie können sie das tun? (Außerdem habe ich das Gefühl, dass der Standard einen geeigneten STL-Container erfordert, der operator[] bereitstellt, damit dieser Operator eine Referenz zurückgibt, aber ich bin mir nicht sicher.)

Sie können möglicherweise umgehen, indem Sie Ihrem Proxy auch eine operator V&() (die den Eintrag erstellen und den Standardwert zuweisen würde), aber ich bin mir nicht sicher, dass dies nicht nur ein weiteres Loop-Loch in einem Fall öffnen würde Ich hatte noch nicht daran gedacht.

std::map löst dieses Problem, indem es angibt, dass die nicht konstante Version dieses Operators immer ein Element einfügt (und überhaupt keine const-Version bereitstellt).

Natürlich kann man immer sagen, dass dies ist nicht ein Off-the-shelf STL-Container, und operator[] nicht zurück Ebene Referenzen Benutzer speichern können. Und vielleicht ist das in Ordnung. Ich habe mich nur gewundert.

+0

Über die Solidität des Designs: Es ist eine Speicheroptimierung eines allgemeinen Vektors, die in bestimmten Fällen verwendet werden soll - also sollte die Schnittstelle/der Vertrag/die Dokumentation das abdecken. Über die Gültigkeit von Referenzen: selbst die STI-Iteratoren sind nicht immer gültig. – xtofl

+2

@xtofl: jedoch definieren STL-Container sehr genau, welche Operationen Iteratoren und/oder Verweise auf Elemente des Containers ungültig machen können. Dieser Container sollte dasselbe tun, und Benutzer müssen sicherstellen, dass alle Vorlagen, die die Klasse als Parameter verwenden, keine Anforderungen stellen, die sie nicht erfüllen können. –