Hinweis Compiler zum Zurückgeben eines Verweises, der 'automatisch' verhält

8

(möglicherweise verwandt mit Wie implementiert man eine C ++ Methode, die ein neues Objekt erzeugt, und gibt einen Verweis darauf zurück , der etwas anderes ist, aber nebenbei fast genau denselben Code enthält)

Ich möchte eine Referenz auf eine statische lokale von einer statischen Funktion zurückgeben. Ich kann es natürlich zur Arbeit bringen, aber es ist weniger schön als ich es möchte.

Kann das verbessert werden?

Der Hintergrund

Ich habe ein paar Klassen, die nicht viel tun, außer eine Ressource auf eine klar definierte Weise und zuverlässig zu erwerben oder zu initialisieren und sie freizugeben. Sie müssen nicht einmal sehr viel über die Ressource selbst wissen, aber der Benutzer möchte vielleicht trotzdem einige Informationen abfragen Das ist natürlich trivial:

%Vor%

Trivial. Alternativ würde das auch gut funktionieren:

%Vor%

Außer jetzt ist das Abfragen von Sachen ein bisschen schwieriger, und es ist weniger hübsch als ich mag. Wenn Sie lieber Stroustrup als Alexandrescu bevorzugen, können Sie stattdessen GSL verwenden und eine Mischung mit final_act verwenden. Was auch immer.

Idealerweise möchte ich etwas schreiben wie:

%Vor%

Hier erhalten Sie einen Verweis auf ein Objekt, das Sie abfragen können, wenn Sie dies wünschen. Oder ignoriere es oder was auch immer. Mein unmittelbarer Gedanke war: Einfach, das ist nur Meyers Singleton in Verkleidung . Also:

%Vor%

Das ist es! Tot einfach und perfekt. Die foo wird erstellt, wenn Sie init aufrufen, Sie erhalten eine bar zurück, die Sie nach Statistiken abfragen können, und es ist eine Referenz, sodass Sie nicht der Eigentümer sind und die foo am Ende automatisch aufräumt.

Außer ...

Das Problem

Natürlich kann nicht so einfach sein, und jeder, der jemals range-based für mit auto verwendet hat, weiß, dass du auto& schreiben musst, wenn du keine Überraschung willst Kopien. Aber leider sah auto allein so unschuldig aus, dass ich nicht daran dachte. Außerdem gebe ich explizit eine Referenz zurück, also was kann auto möglicherweise nur eine Referenz erfassen!

Ergebnis: Eine Kopie wird erstellt (aus was? vermutlich aus der zurückgegebenen Referenz?), die natürlich eine Gültigkeitsdauer hat. Der Standard-Kopierkonstruktor wird aufgerufen (harmlos, tut nichts), schließlich geht die Kopie nicht mehr in den Geltungsbereich, und die Kontexte werden während der Operation freigegeben. Am Programmende wird der Destruktor erneut aufgerufen. Kaboomoom. Wie ist das passiert?

Die offensichtliche (na ja, nicht so offensichtlich in der ersten Sekunde!) Lösung ist zu schreiben:

%Vor%

Das funktioniert und funktioniert gut. Das Problem ist gelöst, außer ... außer es ist nicht schön und die Leute könnten aus Versehen einfach so falsch machen wie ich es getan habe. Können wir nicht ohne ein zusätzliches kaufmännisches Und-Zeichen auskommen?

Es würde wahrscheinlich auch funktionieren, ein shared_ptr zurückzugeben, aber das würde eine unnötige dynamische Speicherzuweisung erfordern und was noch schlimmer ist, es wäre "falsch" in meiner Wahrnehmung. Sie nicht teilen das Eigentum, Sie dürfen sich nur etwas ansehen, das jemand anderes besitzt. Ein roher Zeiger? Richtig für die Semantik, aber ... äh.

Indem ich den Kopierkonstruktor lösche, kann ich verhindern, dass unschuldige Benutzer in das vergessene & amp; Trap (dies wird dann einen Compilerfehler verursachen).

Das ist aber immer noch weniger schön als ich es mir gewünscht hätte. Es muss eine Möglichkeit geben, "Dieser Rückgabewert soll als Referenz verwendet werden" an den Compiler? Etwas wie return std::as_reference(b); ?

Ich hatte über einen Trick nachgedacht, bei dem das Objekt "verschoben" wurde, ohne es wirklich zu bewegen, aber der Compiler wird Sie fast sicher nicht nur ein statisches Local bewegen lassen, aber wenn Sie es schaffen, haben Sie entweder Besitzer geändert, oder mit einem move-constructor "fake move" den Destruktor erneut zweimal aufrufen. Also das ist keine Lösung.

Gibt es einen besseren, schöneren Weg, oder muss ich nur mit dem Schreiben von auto& leben?

    
Damon 06.09.2016, 13:24
quelle

5 Antworten

5
  

Etwas wie return std :: as_reference (b);?

Sie meinen wie std::ref ? Dies liefert einen std::reference_wrapper<T> des von Ihnen angegebenen Werts.

%Vor%

Natürlich wird auto den zurückgegebenen Typ auf reference_wrapper<T> und nicht auf T& ableiten. Und während reference_wrapper<T> eine implizite operatorT& hat, bedeutet das nicht, dass der Benutzer sie genau wie eine Referenz verwenden kann. Um auf Mitglieder zugreifen zu können, müssen sie -> oder .get() verwenden.

Dass alles gesagt ist, aber ich glaube, dass dein Denken falsch ist. Der Grund ist, dass auto und auto& etwas sind, was jeder C ++ Programmierer lernen muss, wie man damit umgeht. Die Leute werden nicht ihre Iterator-Typen reference_wrapper s anstelle von T& zurückgeben. Leute benutzen% ccode% in der Regel überhaupt nicht.

Selbst wenn Sie alle Ihre Schnittstellen wie folgt umbrechen, muss der Benutzer schließlich wissen, wann er reference_wrapper verwenden soll. Also wirklich, der Benutzer hat keine Sicherheit erlangt, außerhalb Ihrer speziellen APIs.

    
Nicol Bolas 06.09.2016, 13:38
quelle
5

Das Erzwingen der Erfassung durch den Benutzer erfolgt in drei Schritten.

Zuerst mache das zurückgegebene Ding nicht kopierbar:

%Vor%

Erstellen Sie dann eine kleine Passthrough-Funktion, die Verweise zuverlässig liefert:

%Vor%

Schreiben Sie dann Ihre statische init () -Funktion und geben Sie declltype (auto) zurück:

%Vor%

Vollständige Demo:

%Vor%

Skypjack hat richtig bemerkt, dass init() genauso korrekt geschrieben werden kann, ohne notstd::as_reference() :

%Vor%

Die Klammern um die Rückgabe (b) erzwingen, dass der Compiler eine Referenz zurückgibt.

Mein Problem mit diesem Ansatz ist, dass C ++ - Entwickler oft überrascht sind, dies zu lernen, so dass es von einem weniger erfahrenen Code-Betreuer leicht übersehen werden könnte.

Mein Gefühl ist, dass return notstd::as_reference(b); explizit die Absicht ausdrückt, Betreuer zu schreiben, ähnlich wie std::move() .

    
Richard Hodges 06.09.2016 13:50
quelle
1

Die beste, idiomatischste, lesbarste und nicht überraschendste Sache wäre, dass Sie =delete den Kopierkonstruktor und den Kopierzuweisungsoperator verwenden und einfach eine Referenz wie alle anderen zurückgeben.

Aber sehen Sie, als Sie schlaue Zeiger aufbrachten ...

  

Es würde wahrscheinlich auch funktionieren, ein shared_ptr zurückzugeben, aber das würde eine unnötige dynamische Speicherzuweisung erfordern und was noch schlimmer ist, es wäre "falsch" in meiner Wahrnehmung. Sie nicht teilen das Eigentum, Sie dürfen sich nur etwas ansehen, das jemand anderes besitzt. Ein roher Zeiger? Richtig für die Semantik, aber ... äh.

Ein roher Zeiger wäre hier vollkommen akzeptabel. Wenn Ihnen das nicht gefällt, haben Sie eine Reihe von Optionen, die dem Gedankengang "Zeiger" folgen.

Sie können shared_ptr ohne dynamischen Speicher mit einem benutzerdefinierten Löschprogramm verwenden:

%Vor%

Obwohl der Aufrufer das Eigentumsrecht nicht "teilt", ist es nicht klar, was Besitz bedeutet, wenn das Löschprogramm ein Nicht-Op ist.

Sie könnten eine weak_ptr verwenden, die einen Verweis auf das Objekt enthält, das von shared_ptr :

verwaltet wird %Vor%

Aber in Anbetracht der shared_ptr Destruktor ist ein No-Op, das ist nicht wirklich anders als im vorherigen Beispiel, und es nur für den Benutzer einen unnötigen Aufruf an .lock() .

Sie können unique_ptr ohne dynamischen Speicher mit einem benutzerdefinierten Löschprogramm verwenden:

%Vor%

Dies hat den Vorteil, dass Sie keine bedeutungslose Referenzzählung verwalten müssen, aber die semantische Bedeutung passt auch nicht perfekt: Der Anrufer geht nicht von einer eindeutigen Eigentümerschaft aus, was auch immer Besitz bedeutet.

In Bibliotheksgrundlagen TS v2 können Sie observer_ptr verwenden, was nur ein roher Zeiger ist, der die Absicht ausdrückt, nicht zu besitzen:

%Vor%

Wenn Sie keine dieser Optionen mögen, können Sie natürlich Ihren eigenen intelligenten Zeigertyp definieren.

In einem zukünftigen Standard können Sie möglicherweise eine "intelligente Referenz" definieren, die wie reference_wrapper ohne .get() funktioniert, indem Sie überladene operator. verwenden.

    
Oktalist 06.09.2016 20:00
quelle
1

Wenn Sie Singleton verwenden möchten, verwenden Sie es richtig

%Vor%

Sie können also keine Kopie erstellen

%Vor%

wo Sie Instanz verwenden können.

%Vor%

Aber wenn Sie RAII anstelle von Singleton verwenden möchten, können Sie dies tun

%Vor%

Mit der Verwendung

%Vor%

Und kopieren ist immer noch verboten

%Vor%     
Jarod42 06.09.2016 18:45
quelle
0

Jemand würde an einem Tag einen Tippfehler machen und dann könnten wir es vielleicht oder auch nicht in so vielen Codes bemerken, obwohl wir alle wissen, dass wir Auto & Amp; anstelle von Auto.

Die bequemste, aber sehr gefährliche Lösung ist die Verwendung einer abgeleiteten Klasse, da sie die strenge Aliasing-Regel durchbricht.

%Vor%     
Landersing 17.11.2016 13:35
quelle

Tags und Links