string c_str () a data ()

102

Przeczytałem kilka miejsc, w których różnica między c_str()i data()(w STL i innych implementacjach) jest taka, że c_str()jest zawsze zakończona wartością null, podczas gdy data()nie jest. O ile widziałem w rzeczywistych implementacjach, albo robią to samo, albo data()wywołują c_str().

Czego tu brakuje? Który z nich jest bardziej odpowiedni do wykorzystania w jakich scenariuszach?

Wilk
źródło

Odpowiedzi:

105

Dokumentacja jest poprawna. Użyj, c_str()jeśli chcesz łańcuch zakończony wartością null.

Jeśli realizatorzy wydarzyło wdrożyć data()w kategoriach c_str()nie trzeba się martwić, nadal używać data(), jeśli nie trzeba ciąg być zerowa zakończone w pewnym realizacji może okazać się skuteczniejsze niż c_str ().

łańcuchy niekoniecznie muszą składać się z danych znakowych, mogą składać się z elementów dowolnego typu. W takich przypadkach data()ma to większe znaczenie. c_str()moim zdaniem jest naprawdę przydatne tylko wtedy, gdy elementy twojego ciągu są oparte na znakach.

Dodatkowo : od C ++ 11 obie funkcje muszą być takie same. tj. datamusi być teraz zakończony wartością zerową. Zgodnie z cppreference : " Zwracana tablica jest zakończona znakiem null, to znaczy data () i c_str () pełnią tę samą funkcję."

Scott Langham
źródło
4
Extra 2: W C ++ 17 i nowszych istnieje teraz również przeciążenie .data()inne niż stałe dla , więc nie są one już równoważne dla niestałych ciągów.
Deduplicator,
29

W C ++ 11 / C ++ 0x , data()i c_str()nie jest już inna. W związku z tym data()wymagane jest również zerowe zakończenie na końcu.

21.4.7.1 basic_stringakcesory [string.accessors]

const charT* c_str() const noexcept;

const charT* data() const noexcept;

1 Zwraca: wskaźnik p taki, że p + i == &operator[](i)dla każdego iw [0,size()].


21.4.5 dostęp do elementu basic_string [string.access]

const_reference operator[](size_type pos) const noexcept;

1 Wymaga: pos <= size (). 2 Zwraca:, w *(begin() + pos) if pos < size()przeciwnym razie odniesienie do obiektu typu T o wartości, charT();do której się odnosi, nie będzie modyfikowane.

mfazekas
źródło
Co się stanie, jeśli ciąg składa się z danych niebędących znakami, co jest dozwolone w przypadku danych ciągu AFAIK, w tym wartości null?
taz
3
@taz Nawet podczas przechowywania danych binarnych, C ++ 11 wymaga std::stringprzydzielenia dodatkowego chardla końcowego '\0'. Kiedy to zrobisz std::string s("\0");, obie wartości s.data()[0]i s.data()[1]zostaną ocenione na 0.
bcrist
19

Nawet wiedząc, że widzieliście, że robią to samo lub że .data () wywołuje .c_str (), nie jest poprawne zakładanie, że tak będzie w przypadku innych kompilatorów. Możliwe jest również, że Twój kompilator zmieni się w przyszłej wersji.

2 powody, dla których warto używać std :: string:

std :: string może być używany zarówno dla tekstu, jak i dowolnych danych binarnych.

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

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

Powinieneś użyć metody .c_str (), gdy używasz swojego ciągu jako przykładu 1.

Powinieneś użyć metody .data (), gdy używasz swojego ciągu jako przykładu 2. Nie dlatego, że użycie .c_str () jest niebezpieczne w takich przypadkach, ale ponieważ jest bardziej wyraźne, że pracujesz z danymi binarnymi dla innych przeglądających Twój kod.

Możliwa pułapka z użyciem .data ()

Poniższy kod jest nieprawidłowy i może spowodować awarię w programie:

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

Dlaczego często implementujące programy powodują, że .data () i .c_str () robią to samo?

Ponieważ jest to bardziej wydajne. Jedynym sposobem, aby .data () zwróciło coś, co nie jest zakończone wartością zerową, byłoby skopiowanie ich wewnętrznego bufora .c_str () lub .data () lub po prostu użycie 2 buforów. Posiadanie pojedynczego bufora zakończonego wartością null zawsze oznacza, że ​​podczas implementacji std :: string można zawsze użyć tylko jednego buforu wewnętrznego.

Brian R. Bondy
źródło
6
Właściwie celem .data () jest to, że nie powinno kopiować wewnętrznego bufora. Oznacza to, że implementacja nie musi marnować znaku \ 0, dopóki nie będzie potrzebna. Nigdy nie chciałbyś mieć dwóch buforów: jeśli wywołujesz .c_str (), dodaj \ 0 do bufora. .data () nadal może zwracać ten bufor.
MSalters
2
Uzgodniono, że używanie 2 buforów byłoby śmieszne. Skąd wiesz, że właśnie dlatego .data było przeznaczone?
Brian R. Bondy
@ BrianR.Bondy Wypróbowałem ten kod: .. auto str = string {"Test \ 0String!" }; cout << "DANE:" << str.data () << endl; Wynik to „Test”, a nie cały ciąg. Co zrobiłem źle?
programista
Ostatnia część jest błędna, data i c_str mogą używać tego samego bufora bez zakończenia zerowego - c_str może po prostu dodać 0 przy pierwszym wywołaniu.
Pamiętaj Monikę,
uwaga, c ++ 11 utworzył .data () alias dla .c_str ()
hanshenrik
3

Odpowiedź została już udzielona, ​​kilka uwag dotyczących celu: Swoboda wdrażania.

std::stringoperacje - np. iteracja, konkatenacja i mutacja elementów - nie potrzebują terminatora zerowego. O ile nie przekażesz stringfunkcji, która oczekuje łańcucha zakończonego zerem, można ją pominąć.

Pozwoliłoby to implementacji na współdzielenie przez podciągi rzeczywistych danych ciągu: string::substrmogłoby wewnętrznie przechowywać odniesienie do współdzielonych danych ciągu oraz zakresu początkowego / końcowego, unikając kopiowania (i dodatkowej alokacji) rzeczywistych danych ciągu. Implementacja odroczyłaby kopiowanie do momentu wywołania c_str lub zmodyfikowania któregokolwiek z ciągów. Żadna kopia nie powstałaby, gdyby po prostu przeczytano odpowiednie znaki.

(implementacja kopiowania przy zapisie nie jest zbyt zabawna w środowiskach wielowątkowych, a typowe oszczędności pamięci / alokacji nie są dziś warte bardziej złożonego kodu, więc rzadko się to robi).


Podobnie string::datapozwala na inną reprezentację wewnętrzną, np. Lina (połączona lista segmentów struny). Może to znacznie usprawnić operacje wstawiania / wymiany. ponownie, lista segmentów musiałaby zostać zwinięta do jednego segmentu, gdy wywołujesz c_strlub data.

peterchen
źródło
2

Cytat z 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
źródło
2

Wszystkie poprzednie komendy są spójne, ale chciałbym również dodać, że zaczynając od c ++ 17, str.data () zwraca char * zamiast const char *

Nam Vu
źródło
1
Zarówno, jak consti non-constprzeciążenia są dostępne od C ++ 17.
Gupta