wake-up-neo.com

c_str () vs. data (), wenn es um den Rückgabetyp geht

Nach C++ 11 dachte ich an c_str() und data()äquivalent .

C++ 17 führt eine Überladung für letzteres ein, die einen nicht konstanten Zeiger zurückgibt ( Referenz , wobei ich nicht sicher bin, ob er vollständig mit C++ 17 aktualisiert wurde):

const CharT* data() const;    (1)   
CharT* data();                (2)   (since C++17)

c_str() gibt nur einen konstanten Zeiger zurück:

const CharT* c_str() const;

Warum die Unterscheidung dieser beiden Methoden in C++ 17, insbesondere wenn C++ 11 sie homogen machte? Mit anderen Worten, warum wurde nur die eine Methode überlastet, die andere nicht?

38
gsamaras

Die neue Überladung wurde mit P0272R1 für C++ 17 hinzugefügt. Weder das Papier selbst noch die darin enthaltenen Links diskutieren, warum nur data neue Überladungen erhalten hat, c_str Jedoch nicht. Wir können an dieser Stelle nur spekulieren (es sei denn, die an der Diskussion Beteiligten mischen sich ein), aber ich möchte die folgenden Punkte zur Berücksichtigung anbieten:

  • Sogar das Hinzufügen der Überladung zu data brachte Code zum Erliegen. Diese Änderung konservativ zu halten war ein Weg, negative Auswirkungen zu minimieren.

  • Die c_str - Funktion war bis jetzt völlig identisch mit data und ist praktisch eine "Legacy" -Funktion zum Verbinden von Code, der "C-String" akzeptiert, dh eine unveränderliche = nullterminiertes Zeichen-Array. Da Sie c_str Immer durch data ersetzen können, gibt es keinen besonderen Grund, diese Legacy-Schnittstelle zu ergänzen.

Mir ist klar, dass die eigentliche Motivation für P0292R1 darin bestand, dass es Legacy-APIs gibt, die fälschlicherweise oder aus C-Gründen nur veränderbare Zeiger verwenden, obwohl sie nicht mutieren. Trotzdem, ich nehme an, wir wollen der bereits umfangreichen API von Strings nicht mehr hinzufügen, als unbedingt notwendig ist.

Noch ein Punkt: ab C++ 17 are now darf write zum Nullterminator, solange Sie schreibe den Wert Null. (Früher war es UB, um irgendetwas in den Nullterminator zu schreiben.) Ein veränderliches c_str Würde einen weiteren Einstiegspunkt in diese besondere Subtilität schaffen, und je weniger Subtilitäten wir haben, desto besser.

22
Kerrek SB

Der Grund, warum das data() -Mitglied eine Überladung bekam, ist in this Paper auf open-std.org erklärt.

TL; DR des Papiers : Die nicht konstante .data() Mitgliedsfunktion für std::string Wurde hinzugefügt, um die Gleichförmigkeit zu verbessern in der Standardbibliothek und um C++ - Entwicklern zu helfen, richtigen Code zu schreiben. Dies ist auch praktisch, wenn Sie eine C-Bibliotheksfunktion aufrufen, deren C-String-Parameter keine const-Qualifikation aufweisen.

Einige relevante Passagen aus dem Papier:

Zusammenfassung
Ist das Fehlen einer nicht konstanten .data() Mitgliedsfunktion in std::string Ein Versehen oder ein beabsichtigtes Design, das auf der Semantik von std::string Vor C++ 11 basiert? ? In beiden Fällen führt dieser Mangel an Funktionalität dazu, dass Entwickler in mehreren legitimen Szenarien unsichere Alternativen verwenden. In diesem Artikel wird die Hinzufügung einer nicht konstanten .data() - Memberfunktion für std :: string empfohlen, um die Einheitlichkeit in der Standardbibliothek zu verbessern und C++ - Entwicklern zu helfen, korrekten Code zu schreiben.

Anwendungsfälle
C-Bibliotheken enthalten gelegentlich Routinen mit char * -Parametern. Ein Beispiel ist der Parameter lpCommandLine der Funktion CreateProcess in der Windows-API. Da das data() -Mitglied von std::string Const ist, kann es nicht verwendet werden, um std :: string-Objekte mit dem lpCommandLine -Parameter arbeiten zu lassen. Entwickler sind versucht, stattdessen .front() zu verwenden, wie im folgenden Beispiel gezeigt.

std::string programName;
// ...
if( CreateProcess( NULL, &programName.front(), /* etc. */ ) ) {
  // etc.
} else {
  // handle error
}

Beachten Sie, dass der Ausdruck programName.front() undefiniertes Verhalten verursacht, wenn programName leer ist. Eine temporäre leere C-Zeichenfolge behebt den Fehler.

std::string programName;
// ...

if( !programName.empty() ) { 
  char emptyString[] = {'\0'};    
  if( CreateProcess( NULL, programName.empty() ? emptyString : &programName.front(), /* etc. */ ) ) {
    // etc.
  } else {
    // handle error
  }
}

Wenn es ein nicht-const .data() -Mitglied gäbe, wie es bei std::vector Der Fall ist, wäre der richtige Code unkompliziert.

std::string programName;
// ...
if( !programName.empty() ) {
  char emptyString[] = {'\0'};
  if( CreateProcess( NULL, programName.data(), /* etc. */ ) ) {
    // etc.
  } else {
    // handle error
  }
}

Eine nicht konstante .data() std::string Mitgliedsfunktion ist auch praktisch, wenn eine C-Bibliotheksfunktion aufgerufen wird, deren C-String-Parameter keine konstante Qualifikation aufweisen. Dies ist in älteren Codes üblich und in solchen, die mit älteren C-Compilern portierbar sein müssen.

21
P.W

Es kommt nur auf die Semantik von "was Sie damit machen wollen" an. Im Allgemeinen wird std::string Manchmal als Puffervektor verwendet, d. H. Als Ersatz für std::vector<char>. Dies ist häufig in boost::asio Zu sehen. Mit anderen Worten, es ist ein Array von Zeichen.

c_str(): Streng bedeutet, dass Sie nach einer nullterminierten Zeichenfolge suchen. In diesem Sinne sollten Sie die Daten niemals ändern und die Zeichenfolge niemals als Nicht-Konstante benötigen.

data(): Möglicherweise benötigen Sie die Informationen in der Zeichenfolge als Pufferdaten und sogar als Nichtkonstante. Möglicherweise müssen Sie die Daten ändern, was auch möglich ist, solange die Länge der Zeichenfolge nicht geändert wird.

Die beiden Mitgliedsfunktionen c_str und data von std :: string existieren aufgrund des Verlaufs der Klasse std :: string.

Bis C++ 11 konnte ein std :: string als Copy-on-Write implementiert werden. Die interne Darstellung benötigte keine Nullterminierung des gespeicherten Strings. Die Mitgliedsfunktion c_str hat sichergestellt, dass der zurückgegebene String mit Null beendet wurde. Die Member-Funktion data simlpy hat einen Zeiger auf die gespeicherte Zeichenfolge zurückgegeben, der nicht unbedingt mit Null beendet wurde. - Um sicherzustellen, dass Änderungen an der Zeichenfolge festgestellt wurden, um das Kopieren beim Schreiben zu ermöglichen, mussten beide Funktionen einen Zeiger auf const-Daten zurückgeben.

Dies änderte sich mit C++ 11, als Copy-on-Write für std :: string nicht mehr zulässig war. Da c_str noch benötigt wurde, um eine nullterminierte Zeichenfolge zu liefern, wird die Null immer an die tatsächlich gespeicherte Zeichenfolge angehängt. Andernfalls muss ein Aufruf von c_str möglicherweise die gespeicherten Daten ändern, um die Zeichenfolge auf null zu setzen, was c_str zu einer nicht konstanten Funktion machen würde. Da data einen Zeiger auf die gespeicherte Zeichenfolge liefert, hat er normalerweise die gleiche Implementierung wie c_str. Beide Funktionen bestehen aufgrund der Abwärtskompatibilität weiterhin.

3
CAF