Zeichenfolge c_str () vs. data ()

102

Ich habe an mehreren Stellen gelesen, dass der Unterschied zwischen c_str()und data()(in STL und anderen Implementierungen) darin besteht, dass c_str()immer null terminiert ist, während dies data()nicht der Fall ist. Soweit ich in tatsächlichen Implementierungen gesehen habe, tun sie entweder dasselbe oder data()rufen auf c_str().

Was fehlt mir hier? Welches ist in welchen Szenarien korrekter?

Wolf
quelle

Antworten:

105

Die Dokumentation ist korrekt. Verwenden c_str()Sie diese Option, wenn Sie eine nullterminierte Zeichenfolge möchten.

Wenn die Implementierer data()in Bezug auf die Implementierung zufrieden c_str()waren, müssen Sie sich keine Sorgen machen. Verwenden data()Sie diese Option dennoch, wenn die Zeichenfolge nicht nullterminiert werden muss. In einigen Implementierungen kann sich herausstellen, dass sie eine bessere Leistung als c_str () aufweist.

Zeichenfolgen müssen nicht unbedingt aus Zeichendaten bestehen, sondern können aus Elementen eines beliebigen Typs bestehen. In diesen Fällen data()ist sinnvoller. c_str()Meiner Meinung nach ist es nur dann wirklich nützlich, wenn die Elemente Ihrer Zeichenfolge zeichenbasiert sind.

Extra : Ab C ++ 11 müssen beide Funktionen identisch sein. dh muss datajetzt nullterminiert werden. Laut cppreference : "Das zurückgegebene Array ist nullterminiert, dh data () und c_str () führen dieselbe Funktion aus."

Scott Langham
quelle
4
Extra 2: Ab C ++ 17 gibt es jetzt auch eine nicht .data()konstante Überladung für , sodass sie für nicht konstante Zeichenfolgen nicht mehr gleichwertig sind.
Deduplikator
29

In C ++ 11 / C ++ 0x , data()und c_str()ist nicht mehr anders. Und daher data()muss am Ende auch eine Null-Kündigung erfolgen.

21.4.7.1 basic_stringAccessoren [string.accessors]

const charT* c_str() const noexcept;

const charT* data() const noexcept;

1 Rückgabe: Ein Zeiger p, so dass p + i == &operator[](i)für jeden iin [0,size()].


21.4.5 basic_string element access [string.access]

const_reference operator[](size_type pos) const noexcept;

1 Benötigt: pos <= size (). 2 Rückgabe: *(begin() + pos) if pos < size()Andernfalls wird ein Verweis auf ein Objekt vom Typ T mit dem Wert, auf charT();den verwiesen wird, nicht geändert.

mfazekas
quelle
Was ist, wenn die Zeichenfolge aus Nicht-Zeichendaten besteht, was für Zeichenfolgendaten AFAIK zulässig ist, einschließlich Null?
taz
3
@taz Auch beim Speichern von Binärdaten erfordert C ++ 11, dass für ein Trailing std::stringein Extra zugewiesen wird . Wenn Sie dies tun , werden beide und garantiert auf 0 ausgewertet.char'\0'std::string s("\0");s.data()[0]s.data()[1]
bcrist
19

Selbst wenn Sie wissen, dass sie dasselbe tun oder dass .data () .c_str () aufruft, ist es nicht richtig anzunehmen, dass dies bei anderen Compilern der Fall ist. Es ist auch möglich, dass sich Ihr Compiler mit einer zukünftigen Version ändert.

2 Gründe für die Verwendung von std :: string:

std :: string kann sowohl für Text als auch für beliebige Binärdaten verwendet werden.

//Example 1
//Plain text:
std::string s1;
s1 = "abc";

//Example 2
//Arbitrary binary data:
std::string s2;
s2.append("a\0b\0b\0", 6);

Sie sollten die Methode .c_str () verwenden, wenn Sie Ihre Zeichenfolge als Beispiel 1 verwenden.

Sie sollten die .data () -Methode verwenden, wenn Sie Ihre Zeichenfolge als Beispiel 2 verwenden. Nicht, weil die Verwendung von .c_str () in diesen Fällen gefährlich ist, sondern weil es expliziter ist, dass Sie mit Binärdaten für andere Überprüfungen arbeiten dein Code.

Mögliche Gefahr bei der Verwendung von .data ()

Der folgende Code ist falsch und kann einen Segfault in Ihrem Programm verursachen:

std::string s;
s = "abc";   
char sz[512]; 
strcpy(sz, s.data());//This could crash depending on the implementation of .data()

Warum ist es für Implementierer üblich, dass .data () und .c_str () dasselbe tun?

Weil es effizienter ist. Die einzige Möglichkeit, .data () dazu zu bringen, etwas zurückzugeben, das nicht nullterminiert ist, besteht darin, .c_str () oder .data () ihren internen Puffer kopieren zu lassen oder nur 2 Puffer zu verwenden. Ein einzelner nullterminierter Puffer bedeutet immer, dass Sie bei der Implementierung von std :: string immer nur einen internen Puffer verwenden können.

Brian R. Bondy
quelle
6
Tatsächlich besteht der Punkt von .data () darin, dass der interne Puffer nicht kopiert werden soll. Dies bedeutet, dass eine Implementierung kein Zeichen für \ 0 verschwenden muss, bis es benötigt wird. Sie möchten niemals zwei Puffer: Wenn Sie .c_str () aufrufen, fügen Sie dem Puffer eine \ 0 hinzu. .data () kann diesen Puffer weiterhin zurückgeben.
MSalters
2
Vollständig einverstanden wäre es lächerlich, 2 Puffer zu verwenden. Woher weißt du, dass .data deshalb beabsichtigt war?
Brian R. Bondy
@ BrianR.Bondy Ich habe diesen Code ausprobiert: .. auto str = string {"Test \ 0String!" }; cout << "DATA:" << str.data () << endl; Die Ausgabe ist "Test" und nicht die gesamte Zeichenfolge. Was habe ich falsch gemacht?
Programmierer
Der letzte Teil ist falsch, Daten und c_str könnten denselben Puffer verwenden, ohne dass er mit 0 beendet wird - c_str könnte einfach die 0 beim ersten Aufruf hinzufügen.
Erinnern Sie sich an Monica
Heads Up, C ++ 11 machte .data () zu einem Alias ​​für .c_str ()
Hanshenrik
3

Es wurden bereits einige Anmerkungen zum Zweck beantwortet: Umsetzungsfreiheit.

std::stringOperationen - z. B. Iteration, Verkettung und Elementmutation - benötigen keinen Nullterminator. Wenn Sie das nicht stringan eine Funktion übergeben, die eine nullterminierte Zeichenfolge erwartet, kann es weggelassen werden.

Dies würde es einer Implementierung ermöglichen, dass Teilzeichenfolgen die tatsächlichen Zeichenfolgendaten gemeinsam nutzen: Sie string::substrkönnten intern einen Verweis auf gemeinsam genutzte Zeichenfolgendaten und den Start- / Endbereich enthalten, wodurch das Kopieren (und die zusätzliche Zuordnung) der tatsächlichen Zeichenfolgendaten vermieden wird. Die Implementierung würde die Kopie verschieben, bis Sie c_str aufrufen oder eine der Zeichenfolgen ändern. Es würde niemals eine Kopie angefertigt werden, wenn die beteiligten Strigns nur gelesen würden.

(Copy-on-Write-Implementierung macht in Multithread-Umgebungen nicht viel Spaß, und die typischen Speicher- / Zuordnungsersparnisse sind den komplexeren Code heutzutage nicht wert, sodass sie selten durchgeführt werden.)


Ebenso string::dataerlaubt eine andere interne Darstellung, zB ein Seil (verknüpfte Liste von Stringsegmenten). Dies kann die Einfüge- / Ersetzungsvorgänge erheblich verbessern. Auch hier müsste die Liste der Segmente beim Aufrufen von c_stroder auf ein einzelnes Segment reduziert werden data.

peterchen
quelle
2

Zitat aus ANSI ISO IEC 14882 2003(C ++ 03 Standard):

    21.3.6 basic_string string operations [lib.string.ops]

    const charT* c_str() const;

    Returns: A pointer to the initial element of an array of length size() + 1 whose first size() elements
equal the corresponding elements of the string controlled by *this and whose last element is a
null character specified by charT().
    Requires: The program shall not alter any of the values stored in the array. Nor shall the program treat the
returned value as a valid pointer value after any subsequent call to a non-const member function of the
class basic_string that designates the same object as this.

    const charT* data() const;

    Returns: If size() is nonzero, the member returns a pointer to the initial element of an array whose first
size() elements equal the corresponding elements of the string controlled by *this. If size() is
zero, the member returns a non-null pointer that is copyable and can have zero added to it.
    Requires: The program shall not alter any of the values stored in the character array. Nor shall the program
treat the returned value as a valid pointer value after any subsequent call to a non- const member
function of basic_string that designates the same object as this.
Mihran Hovsepyan
quelle
2

Alle vorherigen Commements sind konsistent, aber ich möchte auch hinzufügen, dass str.data () ab c ++ 17 ein char * anstelle von const char * zurückgibt

Nam Vu
quelle
1
Beide constund non-constÜberladungen sind seit C ++ 17 verfügbar.
Gupta