So erhalten Sie effizient eine "string_view" für einen Teilstring von "std :: string"

76

Wenn ich http://en.cppreference.com/w/cpp/string/basic_string_view als Referenz verwende, sehe ich keine Möglichkeit, dies eleganter zu tun:

std::string s = "hello world!";
std::string_view v = s;
v = v.substr(6, 5); // "world"

Schlimmer noch, der naive Ansatz ist eine Falle und hinterlässt veinen baumelnden Hinweis auf eine vorübergehende:

std::string s = "hello world!";
std::string_view v(s.substr(6, 5)); // OOPS!

Ich erinnere mich an so etwas wie einen Zusatz zur Standardbibliothek, um einen Teilstring als Ansicht zurückzugeben:

auto v(s.substr_view(6, 5));

Ich kann mir folgende Problemumgehungen vorstellen:

std::string_view(s).substr(6, 5);
std::string_view(s.data()+6, 5);
// or even "worse":
std::string_view(s).remove_prefix(6).remove_suffix(1);

Ehrlich gesagt finde ich keines davon sehr nett. Im Moment ist das Beste, was ich mir vorstellen kann, Aliase zu verwenden, um die Dinge einfach weniger ausführlich zu machen.

using sv = std::string_view;
sv(s).substr(6, 5);
sehe sehen
quelle
4
"Ich finde keines davon sehr nett." Was ist los mit dem ersten? Scheint mir völlig klar zu sein. Bearbeiten: Übrigens: Das Kombinieren von zwei Methoden, die einzeln eine klare Bedeutung haben ( string_view(s).substr(...)), scheint besser zu sein als eine einzelne Funktion, die zwei Dinge gleichzeitig .substr_view(...)ausführt ( ), selbst wenn sie existiert.
Arthur Tacca
2
@sehe: Schlägst du vor, dass a zurückgegeben werden std::string::substrsoll std::string_view?
Geza
1
@geza Ich erinnere mich stark an Ergänzungen der std::basic_string<>Benutzeroberfläche, die subtr_viewtatsächlich eine Operation wie hinzufügen würden . Das habe ich in der Frage erwähnt. Ich hatte gehofft, jemand würde antworten und sagen "Das ist Vorschlag Nxxxx, der abgelehnt / in C ++ zz oder TSn akzeptiert wurde"
siehe
1
@ArthurTacca Da die Operation so häufig ist, denke ich, dass es eine 1-Schritt-Operation geben sollte, möglicherweise auch effizienter. Und sicherlich weniger fehleranfällig: coliru.stacked-crooked.com/a/fd180519dc9b2f00 Eine kostenlose Funktion ist jetzt das Beste, was wir tun können (in Abwesenheit von en.wikipedia.org/wiki/Uniform_Function_Call_Syntax )
sehe
4
@sehe Das Ergebnis meines Alarmisten in der cpporg-Gruppe ist, dass niemand sonst zu glauben scheint, dass string_view ein Problem ist. Die Antwort scheint zu sein: "Gib niemals eine string_view zurück" und füge string_view zu der willkürlichen Liste von Klassen hinzu, die man "nur wissen" sollte, um nicht zurückzukehren. In diesem Fall std::string_view::substr()bricht die Methode ihre eigenen Regeln, da sie eine string_view zurückgibt. Ich denke also, der Rat wäre, dies niemals zu tun. Verwenden Sie a std::string.
Richard Hodges

Antworten:

46

Es gibt die Route mit freien Funktionen, aber es sei denn, Sie stellen auch Überladungen für std::stringeine Schlangengrube bereit .

#include <string>
#include <string_view>

std::string_view sub_string(
  std::string_view s, 
  std::size_t p, 
  std::size_t n = std::string_view::npos)
{
  return s.substr(p, n);
}

int main()
{
  using namespace std::literals;

  auto source = "foobar"s;

  // this is fine and elegant...
  auto bar = sub_string(source, 3);

  // but uh-oh...
  bar = sub_string("foobar"s, 3);
}

IMHO ist das gesamte Design von string_view eine Horrorshow, die uns zurück in eine Welt voller Segfaults und verärgerter Kunden führt.

aktualisieren:

Sogar das Hinzufügen von Überladungen für std::stringist eine Horrorshow. Sehen Sie, ob Sie die subtile Segfault-Zeitbombe erkennen können ...

#include <string>
#include <string_view>

std::string_view sub_string(std::string_view s, 
  std::size_t p, 
  std::size_t n = std::string_view::npos)
{
  return s.substr(p, n);
}

std::string sub_string(std::string&& s, 
  std::size_t p, 
  std::size_t n = std::string::npos)
{
  return s.substr(p, n);
}

std::string sub_string(std::string const& s, 
  std::size_t p, 
  std::size_t n = std::string::npos)
{
  return s.substr(p, n);
}

int main()
{
  using namespace std::literals;

  auto source = "foobar"s;
  auto bar = sub_string(std::string_view(source), 3);

  // but uh-oh...
  bar = sub_string("foobar"s, 3);
}

Der Compiler hat hier nichts zu warnen gefunden. Ich bin mir sicher, dass eine Codeüberprüfung dies auch nicht tun würde.

Ich habe es schon einmal gesagt und ich werde es noch einmal sagen, falls jemand im c ++ - Komitee zuschaut, ist das Zulassen impliziter Konvertierungen von std::stringnach std::string_viewein schrecklicher Fehler, der nur dazu dient, c ++ in Verruf zu bringen .

Aktualisieren

Nachdem ich diese (für mich) ziemlich alarmierende Eigenschaft von string_view im cpporg-Message Board angesprochen habe, wurden meine Bedenken mit Gleichgültigkeit beantwortet.

Der Konsens der Ratschläge dieser Gruppe ist, dass std::string_viewniemals von einer Funktion zurückgegeben werden darf, was bedeutet, dass mein erstes Angebot oben eine schlechte Form ist.

Es gibt natürlich keine Compiler-Hilfe, um Zeiten zu erfassen, in denen dies versehentlich passiert (zum Beispiel durch Vorlagenerweiterung).

Daher std::string_viewsollte mit äußerster Sorgfalt vorgegangen werden, da es aus Sicht der Speicherverwaltung einem kopierbaren Zeiger entspricht, der auf den Status eines anderen Objekts zeigt, das möglicherweise nicht mehr vorhanden ist. Im Übrigen sieht es jedoch wie ein Wertetyp aus und verhält sich auch so.

Also Code wie folgt:

auto s = get_something().get_suffix();

Ist sicher, wenn get_suffix()a zurückgegeben wird std::string(entweder nach Wert oder Referenz)

ist aber UB, wenn get_suffix () jemals überarbeitet wird, um a zurückzugeben std::string_view.

Was meiner bescheidenen Ansicht nach bedeutet, dass jeder Benutzercode, in dem zurückgegebene Zeichenfolgen autogespeichert sind, beschädigt wird, wenn die von ihnen aufgerufenen Bibliotheken jemals überarbeitet werden, um std::string_viewanstelle von zurückgegeben zu werden std::string const&.

Zumindest für mich muss "fast immer automatisch" von nun an "fast immer automatisch" werden, außer wenn es sich um Zeichenfolgen handelt.

Richard Hodges
quelle
3
@sehe aktualisiert mit einem noch schlimmeren Szenario - das Ergebnis von guten Absichten, die schief gehen. Erwarten Sie, dies bald in einer Codebasis in Ihrer Nähe zu sehen ...
Richard Hodges
9
@sehe Ich finde es erstaunlich, dass das Komitee dies nicht kommen sah. Es scheint, als hätten sie ausnahmsweise die schnelle Leistungsverbesserung bestehender Algorithmen gegenüber der Codesicherheit geschätzt. Ich fürchte, das ist ein schwerwiegender Fehler.
Richard Hodges
3
Wer sagt, dass sie es nicht kommen sahen? Es müssen Optionen zum Wiegen gewesen sein. Ich meine, string foo(); bool bar(string_view); auto check = bar(foo());ist sicher und vernünftig zuzulassen.
sehe
6
@sehe natürlich verstehe ich die Begründung. Es ist attraktiv. Es könnte sicher erreicht werden, wenn string_view nicht kopierbar und nicht beweglich wäre. Dann könnten Sie keinen von einer Funktion zurückgeben und alles wäre gut.
Richard Hodges
6
@geza Ihre Frage bestätigt mein Anliegen sehr gut. Die zweite Zuordnung zu barist eine string_viewKonstruktion aus einer string_viewKonstruktion aus einer temporären std::string, die jetzt zerstört wurde.
Richard Hodges
14

Sie können den Konvertierungsoperator von std :: string nach std :: string_view verwenden :

std::string s = "hello world!";
std::string_view v = std::string_view(s).substr(6, 5);
CAF
quelle
Dieser Ansatz ist offensichtlich nicht effizient (dh erledigt er die Arbeit, ohne zusätzliche temporäre Puffer zuzuweisen?), Daher wäre ein Kommentar, der den Anspruch auf Effizienz unterstützt, gut. Tatsächlich scheint dieser Ansatz sehr kontraindiziert zu sein, da ver auf einen temporären Puffer verweist, der sofort ungültig wird.
John Doggett
1

Auf diese Weise können Sie effizient eine Unterzeichenfolge string_view erstellen.

#include <string>
inline
std::string_view substr_view(const std::string &s,size_t from,size_t len) {
  if( from>=s.size() ) return {};
  return std::string_view(s.data()+from,std::min(s.size()-from,len));
}

#include <iostream>
int main(void) {
  std::cout << substr_view("abcd",3,11) << "\n";

  std::string s {"0123456789"};
  std::cout << substr_view(s,3,2) << "\n";

  // be cautious about lifetime, as illustrated at https://en.cppreference.com/w/cpp/string/basic_string_view
  std::string_view bad = substr_view("0123456789"s, 3, 2); // "bad" holds a dangling pointer
  std::cout << bad << "\n"; // possible access violation

  return 0;
}
Alexander
quelle
Es könnte eine gute Idee sein, eine "schlechte Verwendung" hervorzuheben, da sich das gegebene Beispiel leicht zu einer entwickeln könnte. Schlug eine Bearbeitung vor, um genau das zu tun.
John Doggett