Korrektes Idiom für std::string-Konstanten?

Lesezeit: 10 Minuten

Benutzer-Avatar
pm100

Ich habe eine Karte, die ein DB-Objekt darstellt. Ich möchte “bekannte” Werte daraus bekommen

 std::map<std::string, std::string> dbo;
 ...
 std::string val = map["foo"];

alles in Ordnung, aber mir fällt auf, dass “foo” bei jedem Aufruf in eine temporäre Zeichenfolge umgewandelt wird. Sicherlich wäre es besser, einen konstanten std::string zu haben (natürlich ist es wahrscheinlich ein kleiner Overhead im Vergleich zu der Festplatten-IO, die gerade das Objekt abgerufen hat, aber ich denke, es ist immer noch eine gültige Frage). Was ist also die korrekte Redewendung für std::string-Konstanten?

zum Beispiel – ich kann haben

 const std::string FOO = "foo";

in einem hdr, aber dann bekomme ich mehrere kopien

BEARBEITEN: Noch keine Antwort hat gesagt, wie std::string-Konstanten deklariert werden. Ignorieren Sie das ganze Problem mit Karte, STL usw. Viel Code ist stark std::string-orientiert (meiner ist es sicherlich) und es ist natürlich, Konstanten für sie zu wollen, ohne immer wieder für die Speicherzuweisung zu bezahlen

EDIT2: Zweitfrage, die per PDF von Manuel beantwortet wurde, herausgenommen, Beispiel für schlechte Redewendung hinzugefügt

EDIT3: Zusammenfassung der Antworten. Beachten Sie, dass ich diejenigen nicht aufgenommen habe, die die Erstellung einer neuen Zeichenfolgenklasse vorgeschlagen haben. Ich bin enttäuscht, weil ich gehofft hatte, dass es eine einfache Sache gibt, die nur in der Header-Datei funktioniert (wie const char * const ). Wie auch immer

a) von Markus b

 std::map<int, std::string> dict;
 const int FOO_IDX = 1;
 ....
 dict[FOO_IDX] = "foo";
 ....
 std:string &val = dbo[dict[FOO_IDX]];

b) von vlad

 // str.h
 extern const std::string FOO;
 // str.cpp
 const std::string FOO = "foo";

c) von Roger P

 // really you cant do it

(b) scheint dem, was ich wollte, am nächsten zu kommen, hat aber einen fatalen Fehler. Ich kann keinen statischen Code auf Modulebene haben, der diese Zeichenfolgen verwendet, da sie möglicherweise noch nicht erstellt wurden. Ich habe über (a) nachgedacht und tatsächlich einen ähnlichen Trick beim Serialisieren des Objekts verwendet, den Index anstelle der Zeichenfolge gesendet, aber es schien eine Menge Klempnerarbeit für eine Allzwecklösung zu sein. So leider (c) gewinnt, gibt es nicht einfach const Idiom für std:string

  • In Bezug auf die Bearbeitung habe ich versucht, das zu beheben (“nein, so funktioniert std::string nicht” und “hier ist eine andere Zeichenfolgenklasse”), aber beachten Sie, dass, wenn Sie einfach const std::strings irgendwo als global speichern möchten, bei einer ref-counted implementierung (die gcc bereitstellt und der standard explizit zulässt) müssten sie die allokation nur einmal (beim programmstart) bezahlen. Stellen Sie nur sicher, dass diese Globals sind konst um alle möglichen logischen Fehler leicht zu vermeiden.

    Roger Pate

    22. Februar 2010 um 18:41 Uhr


  • Ihre Antwort war also – es ist nicht möglich. Es war nicht klar. Wenn niemand eine bessere Antwort findet, erhalten Sie das Häkchen

    – pm100

    22. Februar 2010 um 18:43 Uhr


  • Eigentlich gefällt mir Manuels Antwort derzeit. Entschuldigung, dass ich mich nicht klar genug ausgedrückt habe, deshalb habe ich den Kommentar abgegeben, aber ich habe mich auch versehentlich in eine andere Option eingeschlichen. 🙂

    Roger Pate

    22. Februar 2010 um 18:54 Uhr

  • aber er sagt immer noch nicht, wie man std::string-Konstanten macht.

    – pm100

    22. Februar 2010 um 20:13 Uhr


Das Kopieren und Fehlen von “String-Literal-Optimierung” ist genau die Art und Weise, wie std::strings funktionieren, und Sie können nicht genau das bekommen, wonach Sie fragen. Das liegt teilweise daran, dass explizit auf virtuelle Methoden und dtor verzichtet wurde. Die Schnittstelle std::string ist eine Menge kompliziert ohne die sowieso.

Der Standard erfordert eine bestimmte Schnittstelle sowohl für std::string als auch für std::map, und diese Schnittstellen verbieten zufällig die gewünschte Optimierung (als “unbeabsichtigte Folge” ihrer anderen Anforderungen und nicht explizit). Zumindest verbieten sie es, wenn Sie tatsächlich alle groben Details des Standards befolgen möchten. Und das wollen Sie wirklich, besonders wenn es so einfach ist, eine andere String-Klasse für diese spezielle Optimierung zu verwenden.

Diese separate Zeichenfolgenklasse kann diese “Probleme” jedoch lösen (wie Sie sagten, ist dies selten ein Problem), aber leider hat die Welt dies getan number_of_programmers + 1 davon schon. Sogar angesichts dieser Neuerfindung des Rads fand ich es nützlich, eine StaticString-Klasse zu haben, die eine Teilmenge der Schnittstelle von std::string hat: mit begin/end, substr, find usw. Sie verbietet auch Änderungen (und passt zu String-Literalen auf diese Weise), speichert nur einen Zeichenzeiger und eine Größe. Sie müssen etwas aufpassen, dass es nur mit Zeichenfolgenliteralen oder anderen “statischen” Daten initialisiert wird, aber das wird durch die Konstruktionsschnittstelle etwas gemildert:

struct StaticString {
  template<int N>
  explicit StaticString(char (&data)[N]); // reference to char array
  StaticString(StaticString const&); // copy ctor (which is very cheap)

  static StaticString from_c_str(char const* c_str); // static factory function
  // this only requires that c_str not change and outlive any uses of the
  // resulting object(s), and since it must also be called explicitly, those 
  // requirements aren't hard to enforce; this is provided because it's explicit
  // that strlen is used, and it is not embedded-'\0'-safe as the
  // StaticString(char (&data)[N]) ctor is

  operator char const*() const; // implicit conversion "operator"
  // here the conversion is appropriate, even though I normally dislike these

private:
  StaticString(); // not defined
};

Verwenden:

StaticString s ("abc");
assert(s != "123"); // overload operators for char*
some_func(s); // implicit conversion
some_func(StaticString("abc")); // temporary object initialized from literal

Beachten Sie, dass der Hauptvorteil dieser Klasse ausdrücklich darin besteht, das Kopieren von Zeichenfolgendaten zu vermeiden, sodass der Zeichenfolgenliteralspeicher wiederverwendet werden kann. Es gibt einen besonderen Platz in der ausführbaren Datei für diese Daten, und sie ist im Allgemeinen gut optimiert, da sie aus den frühesten Tagen von C und darüber hinaus stammt. Tatsächlich glaube ich, dass diese Klasse dem nahe kommt, was String-Literale in C++ hätten sein sollen, wenn da nicht die C-Kompatibilitätsanforderung wäre.

Als Erweiterung könnten Sie auch Ihre eigene Kartenklasse schreiben, wenn dies ein wirklich häufiges Szenario für Sie ist, und das könnte einfacher sein, als Zeichenfolgentypen zu ändern.

  • Wenn es genug Nachfrage gibt, könnte ich diese Klasse von Grund auf neu erfinden, um SO unter der Open-Source-Lizenz von SO zu installieren (normalerweise bin ich damit sehr vorsichtig). Sagen wir, wenn dieser Kommentar +10 Stimmen erhält, dann mache ich es. 🙂

    Roger Pate

    22. Februar 2010 um 18:15 Uhr

  • @RogerPate, du bist jetzt am Haken

    – MM

    27. Dezember 2014 um 8:33 Uhr

Ganz einfach: verwenden

extern const std::string FOO;

in Ihrer Kopfzeile und

const std::string FOO("foo");

im angemessenen .cpp Datei.

  • Was bedeutet „geeignete .cp-Datei“?

    – pm100

    22. Februar 2010 um 20:15 Uhr

  • Für jeden Header (sagen wir, foo.h) hat man meist eine passende .cpp Datei (foo.cpp in unserem Fall). Header können viele Male eingefügt werden, daher enthalten sie normalerweise Datendeklarationen (allerdings mit der üblichen Ausnahme von Template-Klassen). Das .cpp Datei enthält aktuelle Definitionen.

    – Vlad

    22. Februar 2010 um 20:24 Uhr

Benutzer-Avatar
Manuel

  1. Es ist möglich, den Aufwand für die Erstellung einer std::string wenn alles, was Sie wollen, eine konstante Zeichenfolge ist. Dafür müssen Sie jedoch eine spezielle Klasse schreiben, da es in der STL oder in Boost nichts Vergleichbares gibt. Oder eine bessere Alternative ist die Verwendung einer Klasse wie StringPiece aus Chrom bzw StringRef von LLVM. Weitere Informationen finden Sie in diesem verwandten Thread.

  2. Wenn Sie sich entscheiden, bei zu bleiben std::string (was Sie wahrscheinlich tun werden), dann ist eine weitere gute Option die Verwendung des Boost MultiIndex-Containers, der die folgende Funktion hat (quoting die Dokumente):

    Erhöhen Sie den MultiIndex […] stellt Nachschlageoperationen bereit, die Suchschlüssel akzeptieren, die sich vom key_type des Index unterscheiden, was eine besonders nützliche Einrichtung ist, wenn key_type-Objekte teuer zu erstellen sind.

Karten mit teuren Schlüsseln von Andrei Alexandrescu (C/C++-BenutzerjournalFeb. 2006) bezieht sich auf Ihr Problem und ist sehr gut zu lesen.

  • @Manuel du hast recht – das pdf ist eine genaue Übereinstimmung und eine interessante Lektüre. Er diskutiert die ständige Frage nicht

    – pm100

    22. Februar 2010 um 18:37 Uhr

Die richtige Redewendung ist die, die Sie verwenden. In 99,99 % der Fälle müssen Sie sich keine Gedanken über den Overhead des Konstruktors von std::string machen.

Ich frage mich, ob der Konstruktor von std::string von einem Compiler in eine intrinsische Funktion umgewandelt werden könnte? Theoretisch wäre es möglich, aber mein obiger Kommentar wäre Erklärung genug dafür, warum es nicht passiert ist.

Es scheint, dass Sie bereits wissen, was die Zeichenfolgenliterale zur Laufzeit sein werden, sodass Sie eine interne Zuordnung zwischen Aufzählungswerten und einem Array von Zeichenfolgen einrichten können. Dann würden Sie die Enumeration anstelle eines tatsächlichen const char*-Literals in Ihrem Code verwenden.

enum ConstStrings
{
    MAP_STRING,
    FOO_STRING,
    NUM_CONST_STRINGS
};

std::string constStrings[NUM_CONST_STRINGS];

bool InitConstStrings()
{
    constStrings[MAP_STRING] = "map";
    constStrings[FOO_STRING] = "foo";
}

// Be careful if you need to use these strings prior to main being called.
bool doInit = InitConstStrings();

const std::string& getString(ConstStrings whichString)
{
    // Feel free to do range checking if you think people will lie to you about the parameter type.
    return constStrings[whichString];
}

Dann würden Sie sagen map[getString(MAP_STRING)] oder ähnliches.

Erwägen Sie außerdem, den Rückgabewert als konstanten Verweis zu speichern, anstatt ihn zu kopieren, wenn Sie ihn nicht ändern müssen:

const std::string& val = map["foo"];

  • @ pm100: Sie können verschiedene Tricks zur Codegenerierung verwenden, um die Daten im Voraus zu “kennen” — ähnlich wie die gettext i18n-Bibliothek Zeichenfolgen extrahieren kann; Dies ist jedoch eine Menge Arbeit, und Sie können die Zeichenfolgen bei Bedarf einfach manuell in eine Kopfzeile + Implementierung einfügen, da Sie dies nicht häufig tun sollten, da wir über statische Zeichenfolgenkonstanten (Zeichenfolgenliterale mit speziellem syntaktischem Zucker) sprechen , Ja wirklich).

    Roger Pate

    22. Februar 2010 um 22:34 Uhr

  • @ Roger Pate: Ich hatte eigentlich nicht daran gedacht, die Array-Vorlage zu verwenden, und ich mag diese Lösung auch. Sie ändern die Konfiguration nicht oft genug, um das Aktualisieren des N-Parameters schmerzhaft zu machen.

    – Markus B

    23. Februar 2010 um 14:47 Uhr

Benutzer-Avatar
jupp0r

In C ++ 14 können Sie dies tun

const std::string FOO = "foo"s;

  • @ pm100: Sie können verschiedene Tricks zur Codegenerierung verwenden, um die Daten im Voraus zu “kennen” — ähnlich wie die gettext i18n-Bibliothek Zeichenfolgen extrahieren kann; Dies ist jedoch eine Menge Arbeit, und Sie können die Zeichenfolgen bei Bedarf einfach manuell in eine Kopfzeile + Implementierung einfügen, da Sie dies nicht häufig tun sollten, da wir über statische Zeichenfolgenkonstanten (Zeichenfolgenliterale mit speziellem syntaktischem Zucker) sprechen , Ja wirklich).

    Roger Pate

    22. Februar 2010 um 22:34 Uhr

  • @ Roger Pate: Ich hatte eigentlich nicht daran gedacht, die Array-Vorlage zu verwenden, und ich mag diese Lösung auch. Sie ändern die Konfiguration nicht oft genug, um das Aktualisieren des N-Parameters schmerzhaft zu machen.

    – Markus B

    23. Februar 2010 um 14:47 Uhr

Benutzer-Avatar
Thomas Matthäus

Das Problem ist das std::map kopiert den Schlüssel und die Werte in seine eigenen Strukturen.

Du könntest eine haben std::map<const char *, const char *>, aber Sie müssten funktionale Objekte (oder Funktionen) bereitstellen, um die Schlüssel- und Wertdaten zu vergleichen, da diese Schablone für Zeiger gedacht ist. Standardmäßig ist die map würde Zeiger vergleichen und nicht die Daten, auf die die Zeiger zeigen.

Der Kompromiss ist eine einmalige Kopie (std::string) gegenüber dem Zugriff auf einen Komparator (const char *).

Eine andere Alternative ist, eigene zu schreiben map Funktion.

  • Was die erste Zeile betrifft, ist das heutzutage natürlich nicht so schlimm, da wir Bewegungssemantik haben.

    – Unterstrich_d

    11. Februar 2017 um 13:11 Uhr

1015570cookie-checkKorrektes Idiom für std::string-Konstanten?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy