Wann sollte ich string_view in einer Schnittstelle verwenden?

16

Ich verwende eine interne Bibliothek , die zu imitieren eine entworfen wurden vorgeschlagen , C ++ Bibliothek , und irgendwann in den letzten Jahren sehe ich seine Oberfläche verändert verwenden std::stringzu string_view.

Also ändere ich pflichtgemäß meinen Code, um mich an die neue Oberfläche anzupassen. Leider muss ich einen std :: string-Parameter und einen std :: string-Rückgabewert übergeben. Mein Code hat sich also wie folgt geändert:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

zu

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

Ich verstehe wirklich nicht, was mir diese Änderung als API-Client gebracht hat, außer mehr Code (um es möglicherweise zu vermasseln). Der API-Aufruf ist weniger sicher (da die API den Speicher für ihre Parameter nicht mehr besitzt), hat wahrscheinlich die Arbeit meines Programms 0 gespeichert (aufgrund von Optimierungen, die Compiler jetzt ausführen können), und selbst wenn es Arbeit spart, wäre dies nur der Fall ein paar zuweisungen, die nach dem start oder in einer großen schleife irgendwo nicht mehr gemacht werden und nie mehr gemacht werden würden. Nicht für diese API.

Dieser Ansatz scheint jedoch den Ratschlägen zu folgen, die ich an anderer Stelle sehe, zum Beispiel die folgende Antwort :

Abgesehen davon sollten Sie seit C ++ 17 vermeiden, eine const std :: string & zugunsten einer std :: string_view zu übergeben:

Ich finde diesen Rat überraschend, da er das allgemeine Ersetzen eines relativ sicheren Objekts durch ein weniger sicheres (im Grunde genommen ein verherrlichter Zeiger und eine Länge) zu befürworten scheint, vor allem zu Optimierungszwecken.

Wann sollte string_view verwendet werden und wann nicht?

TED
quelle
1
Sie sollten den std::string_viewKonstruktor niemals direkt aufrufen müssen, sondern nur die Zeichenfolgen an die Methode übergeben, die eine std::string_viewdirekte Konvertierung durchführt.
Mgetz
@Mgetz - Hmmm. Ich verwende (noch) keinen vollwertigen C ++ 17-Compiler. Vielleicht ist das das Hauptproblem. Dennoch schien der Beispielcode hier , zumindest bei der Deklaration, den erforderlichen Code anzugeben.
TED
4
Siehe meine Antwort der Konvertierungsoperator ist in der <string>Kopfzeile und geschieht automatisch. Dieser Code täuscht und ist falsch.
Mgetz
1
"mit einem weniger sicheren" wie ist ein Slice weniger sicher als ein Stringverweis?
CodesInChaos
3
@TED ​​Der Aufrufer kann die Zeichenfolge, auf die Ihre Referenz verweist, genauso einfach freigeben wie den Speicher, in den das Slice verweist.
CodesInChaos

Antworten:

18
  1. Muss die Funktionalität, die den Wert verwendet, den Besitz der Zeichenfolge übernehmen? Verwenden Sie in diesem Fall std::string(non-const, non-ref). Mit dieser Option können Sie einen Wert auch explizit verschieben, wenn Sie wissen, dass er im aufrufenden Kontext nie wieder verwendet wird.
  2. Liest die Funktionalität nur den String? Wenn dies der std::string_viewFall ist, verwenden Sie (const, non-ref), da string_viewdies problemlos std::stringund char*ohne Ausgabe und ohne Anfertigung einer Kopie möglich ist. Dies sollte alle const std::string&Parameter ersetzen .

Letztendlich sollten Sie niemals den std::string_viewKonstruktor so aufrufen müssen, wie Sie es sind.std::stringhat einen Konvertierungsoperator , der die Konvertierung automatisch abwickelt.

Mgetz
quelle
Um nur einen Punkt zu verdeutlichen: Ich denke, ein solcher Konvertierungsoperator würde auch die schlimmsten Probleme der Lebensdauer lösen, indem er sicherstellt, dass Ihr RHS-Zeichenfolgenwert über die gesamte Länge des Anrufs hinweg erhalten bleibt.
TED
3
@TED ​​Wenn Sie nur den Wert lesen, überdauert der Wert den Aufruf. Wenn Sie Eigentum übernehmen, muss es den Anruf überdauern. Deshalb habe ich beide Fälle angesprochen. Der Konvertierungsoperator kümmert sich nur std::string_viewdarum, die Verwendung zu vereinfachen. Wenn ein Entwickler es in einer Besitzersituation missbraucht, handelt es sich um einen Programmierfehler. std::string_viewist streng nicht im Besitz.
Mgetz
Warum const, non-ref? Der Parameter const hängt von der jeweiligen Verwendung ab, ist aber im Allgemeinen als nicht const sinnvoll. Und du hast 3
v.oddou
Was ist das Problem der Weitergabe const std::string_view &an Stelle von const std::string &?
Ceztko
@ceztko ist völlig unnötig und fügt beim Zugriff auf die Daten eine zusätzliche Indirektion hinzu.
Mgetz
15

A std::string_viewbringt einige der Vorteile von a const char*in C ++: Im Gegensatz zu std::stringeinem string_view

  • besitzt kein Gedächtnis,
  • reserviert keinen Speicher,
  • kann mit einem gewissen Versatz in eine vorhandene Zeichenfolge zeigen, und
  • hat eine Stufe weniger Zeiger-Indirektion als a std::string&.

Dies bedeutet, dass eine string_view häufig Kopien vermeidet, ohne dass rohe Zeiger verwendet werden müssen.

In modernem Code std::string_viewsollten fast alle Verwendungen von const std::string&Funktionsparametern ersetzt werden. Dies sollte eine quellkompatible Änderung sein, da dies std::stringeinen Konvertierungsoperator zu deklariert std::string_view.

Nur weil eine Zeichenfolgenansicht in Ihrem speziellen Anwendungsfall, in dem Sie ohnehin eine Zeichenfolge erstellen müssen, nicht hilft, bedeutet dies nicht, dass es im Allgemeinen eine schlechte Idee ist. Die C ++ - Standardbibliothek wurde eher aus Gründen der Allgemeinheit als der Benutzerfreundlichkeit optimiert. Das Argument „weniger sicher“ trifft nicht zu, da es nicht erforderlich sein sollte, die Zeichenfolgenansicht selbst zu erstellen.

amon
quelle
2
Der große Nachteil std::string_viewist das Fehlen einer c_str()Methode, was zu unnötigen Zwischenobjekten führt std::string, die konstruiert und zugewiesen werden müssen. Dies ist insbesondere bei Low-Level-APIs ein Problem.
Matthias
1
@Matthias Das ist ein guter Punkt, aber ich denke nicht, dass es ein großer Nachteil ist. In einer Zeichenfolgenansicht können Sie versetzt auf eine vorhandene Zeichenfolge zeigen. Dieser Teilstring kann nicht mit Null terminiert werden, dafür benötigen Sie eine Kopie. Eine Zeichenfolgenansicht verhindert nicht, dass Sie eine Kopie erstellen. Es ermöglicht viele Zeichenfolgenverarbeitungsaufgaben, die mit Iteratoren ausgeführt werden können. Sie haben jedoch Recht, dass APIs, die eine C-Zeichenfolge benötigen, nicht von Views profitieren. Ein Stringverweis kann dann sinnvoller sein.
amon
@Matthias, stimmt string_view :: data () nicht mit c_str () überein?
Aelian
3
@Jeevaka Eine C-Zeichenfolge muss mit Nullen abgeschlossen sein, aber die Daten einer Zeichenfolgenansicht werden normalerweise nicht mit Nullen abgeschlossen, da sie auf eine vorhandene Zeichenfolge verweisen. Beispiel: Wenn wir eine Zeichenfolge abcdef\0und eine Zeichenfolgenansicht haben, die auf die cdeTeilzeichenfolge zeigen, steht nach dem Zeichen " e-" für die ursprüngliche Zeichenfolge kein fNullzeichen. Der Standard stellt außerdem fest: „data () kann einen Zeiger auf einen Puffer zurückgeben, der nicht mit Null terminiert ist. Daher ist es in der Regel ein Fehler, data () an eine Funktion zu übergeben, die nur ein const charT * enthält und eine nullterminierte Zeichenfolge erwartet. ”
amon
1
@kayleeFrye_onDeck Die Daten sind bereits ein Zeichenzeiger. Das Problem mit C-Strings besteht darin, dass kein Zeichenzeiger abgerufen wird, sondern dass ein C-String mit Null terminiert werden muss. Ein Beispiel finden Sie in meinem vorherigen Kommentar.
amon
8

Ich finde diesen Rat überraschend, da er das allgemeine Ersetzen eines relativ sicheren Objekts durch ein weniger sicheres (im Grunde genommen ein verherrlichter Zeiger und eine Länge) zu befürworten scheint, vor allem zu Optimierungszwecken.

Ich denke, das ist ein leichtes Missverständnis des Zwecks. Während es sich um eine "Optimierung" handelt, sollten Sie es sich wirklich so vorstellen, als ob Sie sich von der Verwendung von a abkoppeln müssten std::string.

Benutzer von C ++ haben Dutzende verschiedener Zeichenfolgenklassen erstellt. Zeichenfolgenklassen mit fester Länge, SSO-optimierte Klassen, wobei die Puffergröße ein Vorlagenparameter ist, Zeichenfolgenklassen, die einen Hashwert zum Vergleichen speichern, usw. Manche verwenden sogar COW-basierte Zeichenfolgen. Wenn es eine Sache gibt, die C ++ - Programmierer gerne tun, dann sind es Schreibzeichenfolgenklassen.

Dabei werden Zeichenfolgen ignoriert, die von C-Bibliotheken erstellt wurden und deren Eigentümer sie sind. Nackte char*s, vielleicht mit einer Größe von einer Art.

Wenn Sie also eine Bibliothek schreiben und eine nehmen const std::string&, muss der Benutzer jetzt die Zeichenfolge, die er verwendet hat, nehmen und in eine kopieren std::string. Vielleicht Dutzende Male.

Wenn Sie auf die zeichenfolgenspezifische std::stringSchnittstelle zugreifen möchten , warum sollten Sie die Zeichenfolge kopieren müssen ? Das ist so eine Verschwendung.

Die Hauptgründe, a nicht string_viewals Parameter zu verwenden, sind:

  1. Wenn Ihr Endziel darin besteht, die Zeichenfolge an eine Schnittstelle zu übergeben, die eine mit NUL abgeschlossene Zeichenfolge ( fopenusw.) verwendet. std::stringwird garantiert, um NUL beendet zu werden; string_viewist nicht. Und es ist sehr einfach, eine Ansicht mit Teilzeichenfolgen zu versehen, damit sie nicht mit NUL abgeschlossen wird. Unterzeichenfolge a std::stringkopiert die Teilzeichenfolge in einen mit NUL abgeschlossenen Bereich.

    Für genau dieses Szenario habe ich einen speziellen NUL-terminierten string_view-Stiltyp geschrieben. Sie können die meisten Vorgänge ausführen, jedoch keine Vorgänge, die den NUL-terminierten Status aufheben (z. B. Trimmen vom Ende).

  2. Probleme auf Lebenszeit. Wenn Sie das wirklich kopieren müssen std::stringoder das Array von Zeichen den Funktionsaufruf überlebt haben, geben Sie dies am besten im Voraus mit a an const std::string &. Oder einfach std::stringals Wertparameter. Auf diese Weise können Sie, wenn sie bereits über eine solche Zeichenfolge verfügen, sofort den Besitz der Zeichenfolge beanspruchen, und der Anrufer kann in die Zeichenfolge wechseln, wenn er keine Kopie der Zeichenfolge aufbewahren muss.

Nicol Bolas
quelle
Ist das wahr? Die einzige Standard- String-Klasse, die mir zuvor in C ++ bekannt war, war std :: string. Es gibt eine gewisse Unterstützung für die Verwendung von Zeichen * als "Zeichenketten", um die Abwärtskompatibilität mit C zu gewährleisten, aber das muss ich fast nie verwenden. Sicher, es gibt viele benutzerdefinierte Klassen von Drittanbietern für fast alles, was Sie sich vorstellen können, und Zeichenfolgen sind wahrscheinlich darin enthalten, aber ich muss diese fast nie verwenden.
TED
@TED: Nur weil Sie diese "fast nie verwenden müssen", heißt das nicht, dass andere Leute sie nicht routinemäßig verwenden. string_viewist eine Verkehrssprache, die mit allem umgehen kann.
Nicol Bolas
3
@TED: Deshalb habe ich "C ++ als Programmierumgebung" gesagt, im Gegensatz zu "C ++ als Sprache / Bibliothek".
Nicol Bolas
2
@TED: " Also könnte ich auch sagen" C ++ als Programmierumgebung hat Tausende von Containerklassen "? " Und das tut es. Aber ich kann Algorithmen schreiben, die mit Iteratoren arbeiten, und alle Containerklassen, die diesem Paradigma folgen, funktionieren mit ihnen. Im Gegensatz dazu waren "Algorithmen", die ein zusammenhängendes Array von Zeichen aufnehmen können, viel schwieriger zu schreiben. Mit string_viewist es einfach.
Nicol Bolas
1
@TED: Zeichenarrays sind ein ganz besonderer Fall. Sie sind außerordentlich häufig, und verschiedene Container zusammenhängender Zeichen unterscheiden sich nur darin, wie sie ihren Speicher verwalten, nicht darin, wie Sie die Daten durchlaufen. Es ist also sinnvoll, einen einzigen Lingua-Franca-Bereichstyp zu haben, der alle diese Fälle abdecken kann, ohne eine Vorlage verwenden zu müssen. Verallgemeinerung darüber hinaus ist die Provinz der Range TS und Vorlagen.
Nicol Bolas