Rozważ następujący program:
#include <iostream>
int main()
{
int x { 5 }; // x makes a copy of its initializer
std::cout << x << '\n';
return 0;
}Gdy wykonywana jest definicja dla x , wartość inicjująca 5 jest kopiowana do pamięci przeznaczonej dla zmienna int x. W przypadku typów podstawowych inicjowanie i kopiowanie zmiennej jest szybkie.
Rozważmy teraz podobny program:
#include <iostream>
#include <string>
int main()
{
std::string s{ "Hello, world!" }; // s makes a copy of its initializer
std::cout << s << '\n';
return 0;
}Po wywołaniu s jest inicjowany, literał ciągu znaków w stylu C "Hello, world!" jest kopiowany do pamięci przydzielonej dla std::string s. W przeciwieństwie do typów podstawowych, inicjowanie i kopiowanie a std::string jest powolne.
W powyższym programie jedyne, co robimy za pomocą s to wypisanie wartości do konsoli, a następnie s jest ona niszczona. Zasadniczo stworzyliśmy kopię „Hello, world!” po prostu wydrukować, a następnie zniszczyć tę kopię. To jest nieefektywne.
Widzimy coś podobnego w tym przykładzie:
#include <iostream>
#include <string>
void printString(std::string str) // str makes a copy of its initializer
{
std::cout << str << '\n';
}
int main()
{
std::string s{ "Hello, world!" }; // s makes a copy of its initializer
printString(s);
return 0;
}W tym przykładzie tworzone są dwie kopie ciągu znaków w stylu C „Hello, world!”: jedna, gdy inicjujemy s w main(), a druga, gdy inicjujemy parametr str w printString(). To dużo niepotrzebnego kopiowania, aby wydrukować ciąg znaków!
std::string_view C++17
Aby rozwiązać problem std::string kosztownego inicjalizacji (lub kopiowania), wprowadzono C++17 std::string_view (który znajduje się w nagłówku <string_view>). std::string_view zapewnia dostęp tylko do odczytu istniejący ciąg znaków (ciąg w stylu C, a std::string lub innymi std::string_view) bez tworzenia kopii. Tylko do odczytu oznacza, że możemy uzyskać dostęp do przeglądanej wartości i z niej korzystać, ale nie możemy jej modyfikować.
Poniższy przykład jest identyczny z poprzedni, z tą różnicą, że zastąpiliśmy std::string z std::string_view.
#include <iostream>
#include <string_view> // C++17
// str provides read-only access to whatever argument is passed in
void printSV(std::string_view str) // now a std::string_view
{
std::cout << str << '\n';
}
int main()
{
std::string_view s{ "Hello, world!" }; // now a std::string_view
printSV(s);
return 0;
}Ten program generuje takie same dane wyjściowe jak poprzedni, ale nie kopiuje ciągu „Witaj, świecie!” są tworzone.
Kiedy inicjujemy std::string_view s za pomocą literału łańcuchowego w stylu C "Hello, world!", s zapewnia dostęp tylko do odczytu do „Hello, world!” bez tworzenia kopii ciągu. Kiedy przekazujemy s Do printSV(), parametr str jest inicjowany z s. Dzięki temu możemy uzyskać dostęp do „Hello, world!” poprzez str, ponownie bez tworzenia kopii ciągu.
Najlepsza praktyka
Preferuj std::string_view za std::string kiedy potrzebny jest ciąg tylko do odczytu, zwłaszcza dla parametrów funkcji.
std::string_view można inicjalizować wieloma różnymi typami ciągów
Jedną z najważniejszych cech std::string_view jest jego elastyczność. Obiekt std::string_view można zainicjować ciągiem w stylu C, a std::string lub innymi std::string_view:
#include <iostream>
#include <string>
#include <string_view>
int main()
{
std::string_view s1 { "Hello, world!" }; // initialize with C-style string literal
std::cout << s1 << '\n';
std::string s{ "Hello, world!" };
std::string_view s2 { s }; // initialize with std::string
std::cout << s2 << '\n';
std::string_view s3 { s2 }; // initialize with std::string_view
std::cout << s3 << '\n';
return 0;
}std::string_view parametrami, które akceptują wiele różnych typów argumentów ciągu
Zarówno ciąg w stylu C, jak i a std::string będą niejawnie konwertowane na a std::string_view. Zatem parametr std::string_view zaakceptuje argumenty typu string w stylu C, a std::string lub std::string_view:
#include <iostream>
#include <string>
#include <string_view>
void printSV(std::string_view str)
{
std::cout << str << '\n';
}
int main()
{
printSV("Hello, world!"); // call with C-style string literal
std::string s2{ "Hello, world!" };
printSV(s2); // call with std::string
std::string_view s3 { s2 };
printSV(s3); // call with std::string_view
return 0;
}std::string_view nie zostanie niejawnie przekonwertowany na std::string
Ponieważ std::string utworzy kopię swojego inicjatora (co jest drogie), C++ nie pozwoli na niejawną konwersję a std::string_view do std::string. Ma to na celu zapobieżenie przypadkowemu przekazaniu std::string_view do std::string parametru i przypadkowemu wykonaniu kosztownej kopii, gdy taka kopia może nie być wymagana.
Jeśli jednak jest to pożądane, mamy dwie możliwości:
- Jawnie utwórz
std::stringzstd::string_viewinicjator (co jest dozwolone, ponieważ rzadko będzie to zrobione w sposób niezamierzony) - Konwertuj istniejący
std::string_viewdostd::stringza pomocąstatic_cast
Poniższy przykład pokazuje obie opcje:
#include <iostream>
#include <string>
#include <string_view>
void printString(std::string str)
{
std::cout << str << '\n';
}
int main()
{
std::string_view sv{ "Hello, world!" };
// printString(sv); // compile error: won't implicitly convert std::string_view to a std::string
std::string s{ sv }; // okay: we can create std::string using std::string_view initializer
printString(s); // and call the function with the std::string
printString(static_cast<std::string>(sv)); // okay: we can explicitly cast a std::string_view to a std::string
return 0;
}Przypisanie zmienia to, co przegląda std::string_view
Przypisanie nowego ciągu do std::string_view powoduje std::string_view wyświetlenie nowego ciągu. Nie modyfikuje to w żaden sposób poprzednio przeglądanego ciągu.
Poniższy przykład ilustruje to:
#include <iostream>
#include <string>
#include <string_view>
int main()
{
std::string name { "Alex" };
std::string_view sv { name }; // sv is now viewing name
std::cout << sv << '\n'; // prints Alex
sv = "John"; // sv is now viewing "John" (does not change name)
std::cout << sv << '\n'; // prints John
std::cout << name << '\n'; // prints Alex
return 0;
}W powyższym przykładzie sv = "John" powoduje sv wyświetlanie ciągu "John". Nie zmienia to wartości utrzymywanej przez name (która nadal jest "Alex").
Literały dla std::string_view
Literały łańcuchowe w podwójnych cudzysłowach są domyślnie literałami łańcuchowymi w stylu C. Literały łańcuchowe możemy tworzyć za pomocą typu std::string_view używając a sv sufiks po literale ciągu w cudzysłowie sv musi być pisany małymi literami.
#include <iostream>
#include <string> // for std::string
#include <string_view> // for std::string_view
int main()
{
using namespace std::string_literals; // access the s suffix
using namespace std::string_view_literals; // access the sv suffix
std::cout << "foo\n"; // no suffix is a C-style string literal
std::cout << "goo\n"s; // s suffix is a std::string literal
std::cout << "moo\n"sv; // sv suffix is a std::string_view literal
return 0;
}Powiązana treść
Omawiamy użycie using namespace w lekcji 5.7 — Wprowadzenie do std::string. Ta sama rada ma zastosowanie tutaj.
Dobrze jest zainicjować obiekt std::string_view za pomocą Literał łańcuchowy w stylu C (nie trzeba go inicjować za pomocą literału std::string_view ).
To powiedziawszy, inicjowanie std::string_view użycie std::string_view literału nie spowoduje problemów (ponieważ takie literały są w rzeczywistości literałami łańcuchowymi w stylu C).
constexpr std::string_view
W przeciwieństwie do std::string, std::string_view w pełni obsługuje constexpr:
#include <iostream>
#include <string_view>
int main()
{
constexpr std::string_view s{ "Hello, world!" }; // s is a string symbolic constant
std::cout << s << '\n'; // s will be replaced with "Hello, world!" at compile-time
return 0;
}To sprawia, że constexpr std::string_view jest preferowanym wyborem, gdy potrzebne są symboliczne stałe łańcuchowe.
Będziemy kontynuować dyskusję std::string_view .

