Khi nào tôi nên sử dụng string_view trong giao diện?


16

Tôi đang sử dụng một thư viện nội bộ được thiết kế để bắt chước thư viện C ++ được đề xuất và đôi khi trong vài năm qua tôi thấy giao diện của nó thay đổi từ sử dụng std::stringsang string_view.

Vì vậy, tôi mạnh dạn thay đổi mã của mình, để phù hợp với giao diện mới. Thật không may, những gì tôi phải truyền vào là một tham số std :: string và một cái gì đó là giá trị trả về std :: string. Vì vậy, mã của tôi đã thay đổi từ một cái gì đó như thế này:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

đến

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

Tôi thực sự không thấy những thay đổi này đã mua cho tôi với tư cách là ứng dụng khách API, ngoài nhiều mã hơn (để có thể làm hỏng). Cuộc gọi API kém an toàn hơn (do API không còn sở hữu bộ nhớ cho các tham số của nó), có thể đã lưu chương trình 0 của tôi (do trình biên dịch tối ưu hóa di chuyển có thể thực hiện ngay bây giờ) và ngay cả khi nó đã lưu công việc, điều đó chỉ có thể một vài phân bổ sẽ không và sẽ không bao giờ được thực hiện sau khi khởi động hoặc trong một vòng lặp lớn ở đâu đó. Không dành cho API này.

Tuy nhiên, cách tiếp cận này dường như làm theo lời khuyên tôi thấy ở nơi khác, ví dụ câu trả lời này :

Bên cạnh đó, vì C ++ 17, bạn nên tránh truyền const std :: string & có lợi cho std :: string_view:

Tôi thấy lời khuyên đó đáng ngạc nhiên, vì dường như nó đang ủng hộ việc thay thế một đối tượng tương đối an toàn bằng một vật thể kém an toàn hơn (về cơ bản là một con trỏ và chiều dài được tôn vinh), chủ yếu cho mục đích tối ưu hóa.

Vì vậy, khi nào nên sử dụng string_view, và khi nào thì không?


1
bạn không bao giờ phải gọi hàm std::string_viewtạo trực tiếp, bạn chỉ cần truyền các chuỗi cho phương thức lấy std::string_viewtrực tiếp và nó sẽ tự động chuyển đổi.
Mgetz

@Mgetz - Hừm. Tôi chưa (chưa) sử dụng trình biên dịch C ++ 17 đầy đủ, nên có lẽ đó là vấn đề. Tuy nhiên, mã mẫu ở đây dường như chỉ ra yêu cầu của nó, ít nhất là khi khai báo một mã.
TED

4
Xem câu trả lời của tôi, toán tử chuyển đổi nằm trong <string>tiêu đề và tự động xảy ra. Mã đó là lừa dối và sai.
Mgetz

1
"với một cái kém an toàn hơn" làm thế nào một lát cắt kém an toàn hơn một tham chiếu chuỗi?
CodeInChaos

3
@TED ​​Người gọi có thể dễ dàng giải phóng chuỗi mà tham chiếu của bạn đang trỏ tới vì họ có thể giải phóng bộ nhớ mà lát đang trỏ vào.
CodeInChaos

Câu trả lời:


18
  1. Có phải chức năng lấy giá trị cần phải có quyền sở hữu chuỗi? Nếu vậy sử dụng std::string(không const, không ref). Tùy chọn này cho bạn lựa chọn di chuyển rõ ràng trong một giá trị nếu bạn biết rằng nó sẽ không bao giờ được sử dụng lại trong ngữ cảnh gọi.
  2. Có chức năng chỉ đọc chuỗi? Nếu vậy sử dụng std::string_view(const, non-ref) điều này là bởi vì string_viewcó thể xử lý std::stringchar*dễ dàng mà không có vấn đề và không tạo ra một bản sao. Điều này sẽ thay thế tất cả các const std::string&tham số.

Cuối cùng, bạn không bao giờ cần phải gọi nhà std::string_viewxây dựng như bạn. std::stringcó một toán tử chuyển đổi tự động xử lý chuyển đổi.


Chỉ cần làm rõ một điểm, tôi nghĩ rằng một nhà điều hành chuyển đổi như vậy cũng sẽ giải quyết vấn đề tồi tệ nhất trong các vấn đề trọn đời, bằng cách đảm bảo giá trị chuỗi RHS của bạn duy trì trong suốt thời gian của cuộc gọi?
TED

3
@TED ​​nếu bạn chỉ đọc giá trị thì giá trị sẽ tồn tại lâu hơn cuộc gọi. Nếu bạn đang sở hữu thì nó cần tồn tại lâu hơn cuộc gọi. Do đó tại sao tôi giải quyết cả hai trường hợp. Toán tử chuyển đổi chỉ liên quan đến việc làm cho std::string_viewdễ sử dụng hơn. Nếu một nhà phát triển sử dụng sai nó trong tình huống sở hữu đó là lỗi lập trình. std::string_viewlà hoàn toàn không sở hữu.
Mgetz

Tại sao const, non-ref? Tham số const là tùy thuộc vào việc sử dụng cụ thể, nhưng nói chung là hợp lý như không const. Và bạn đã bỏ lỡ 3. Có thể chấp nhận lát
v.oddou

Vấn đề của việc vượt qua const std::string_view &thay thế là const std::string &gì?
ceztko

@ceztko nó hoàn toàn không cần thiết và thêm một sự gián tiếp khi truy cập dữ liệu.
Mgetz

15

A std::string_viewmang lại một số lợi ích của a const char*cho C ++: không giống như std::string, chuỗi_view

  • không có bộ nhớ
  • không phân bổ bộ nhớ,
  • có thể trỏ vào một chuỗi hiện có ở một số bù và
  • có một mức độ ít hơn của con trỏ không xác định so với a std::string&.

Điều này có nghĩa là một chuỗi_view thường có thể tránh các bản sao mà không phải xử lý các con trỏ thô.

Trong mã hiện đại, std::string_viewnên thay thế gần như tất cả việc sử dụng các const std::string&tham số chức năng. Đây phải là một thay đổi tương thích với nguồn, vì std::stringkhai báo một toán tử chuyển đổi thành std::string_view.

Chỉ vì chế độ xem chuỗi không giúp ích trong trường hợp sử dụng cụ thể của bạn khi bạn cần tạo chuỗi dù sao không có nghĩa là đó là một ý tưởng tồi nói chung. Thư viện chuẩn C ++ có xu hướng được tối ưu hóa cho tính tổng quát hơn là để thuận tiện. Các đối số ít an toàn hơn không giữ được, vì không cần thiết phải tự tạo chế độ xem chuỗi.


2
Hạn chế lớn của std::string_viewviệc không có c_str()phương pháp, dẫn đến std::stringcác đối tượng trung gian không cần thiết cần được xây dựng và phân bổ. Điều này đặc biệt là một vấn đề trong các API cấp thấp.
Matthias

1
@Matthias Đó là một điểm tốt, nhưng tôi không nghĩ đó là một nhược điểm lớn. Chế độ xem chuỗi cho phép bạn trỏ vào một chuỗi hiện có ở một số bù. Chuỗi con đó không thể kết thúc bằng 0, bạn cần một bản sao cho điều đó. Chế độ xem chuỗi không cấm bạn tạo bản sao. Nó cho phép nhiều tác vụ xử lý chuỗi có thể được thực hiện với các trình vòng lặp. Nhưng bạn đã đúng rằng các API cần chuỗi C sẽ không thu được lợi nhuận từ lượt xem. Một tham chiếu chuỗi sau đó có thể thích hợp hơn.
amon

@Matthias, không String_view :: data () khớp với c_str ()?
Aelian

3
@Jeevaka một chuỗi C phải được kết thúc bằng 0, nhưng dữ liệu của chế độ xem chuỗi thường không bị chấm dứt bởi vì nó trỏ vào một chuỗi hiện có. Ví dụ: nếu chúng ta có một chuỗi abcdef\0và chế độ xem chuỗi trỏ vào cdechuỗi con, không có ký tự 0 nào sau echuỗi - chuỗi gốc có fở đó. Các tiêu chuẩn cũng lưu ý: “dữ liệu () có thể trả về một con trỏ đến một bộ đệm đó không phải là null-chấm dứt. Vì vậy nó thường là một sai lầm để truyền dữ liệu () để một chức năng mà chỉ mất một biểu đồ const * và sẽ là một chuỗi null-chấm dứt “.
amon

1
@kayleeFrye_onDeck Dữ liệu đã là một con trỏ char. Vấn đề với chuỗi C là không nhận được con trỏ char, nhưng chuỗi C phải được kết thúc bằng null. Xem bình luận trước của tôi cho một ví dụ.
amon

8

Tôi thấy lời khuyên đó đáng ngạc nhiên, vì dường như nó đang ủng hộ việc thay thế một đối tượng tương đối an toàn bằng một vật thể kém an toàn hơn (về cơ bản là một con trỏ và chiều dài được tôn vinh), chủ yếu cho mục đích tối ưu hóa.

Tôi nghĩ rằng điều này là hơi hiểu lầm mục đích của việc này. Mặc dù nó là một "tối ưu hóa", nhưng bạn thực sự nên nghĩ về nó như giải quyết bản thân khỏi phải sử dụng a std::string.

Người dùng C ++ đã tạo ra hàng tá các lớp chuỗi khác nhau. Các lớp chuỗi có độ dài cố định, các lớp được tối ưu hóa SSO với kích thước bộ đệm là tham số mẫu, các lớp chuỗi lưu trữ giá trị băm được sử dụng để so sánh chúng, v.v. Một số người thậm chí sử dụng chuỗi dựa trên COW. Nếu có một điều mà các lập trình viên C ++ thích làm, đó là viết các lớp chuỗi.

Và bỏ qua các chuỗi được tạo và sở hữu bởi các thư viện C. Khỏa thân char*, có thể với một kích thước của một số loại.

Vì vậy, nếu bạn đang viết một số thư viện và bạn lấy một const std::string&, người dùng bây giờ phải lấy bất kỳ chuỗi nào họ đang sử dụng và sao chép nó vào một std::string. Có thể hàng chục lần.

Nếu bạn muốn truy cập vào std::stringgiao diện dành riêng cho chuỗi, tại sao bạn phải sao chép chuỗi? Đó là một sự lãng phí.

Những lý do chính yếu để không lấy string_viewtham số là:

  1. Nếu mục tiêu cuối cùng của bạn là chuyển chuỗi đến một giao diện có chuỗi kết thúc NUL ( fopen, v.v.). std::stringđược đảm bảo chấm dứt NUL; string_viewkhông phải. Và thật dễ dàng để phân nhóm một khung nhìn để làm cho nó không bị chấm dứt NUL; chuỗi con a std::stringsẽ sao chép chuỗi con ra thành một phạm vi kết thúc NUL.

    Tôi đã viết một kiểu kiểu string_view kết thúc NUL đặc biệt cho chính xác kịch bản này. Bạn có thể thực hiện hầu hết các thao tác, nhưng không thực hiện các thao tác phá vỡ trạng thái kết thúc NUL của nó (ví dụ như cắt xén từ cuối).

  2. Vấn đề trọn đời. Nếu bạn thực sự cần phải sao chép std::stringhoặc có các ký tự khác ngoài cuộc gọi hàm, tốt nhất là nêu rõ điều này bằng cách thực hiện const std::string &. Hoặc chỉ std::stringlà một tham số giá trị. Theo cách đó, nếu họ đã có một chuỗi như vậy, bạn có thể yêu cầu quyền sở hữu chuỗi đó ngay lập tức và người gọi có thể chuyển sang chuỗi nếu họ không cần giữ một bản sao của chuỗi đó.


Điều này có đúng không? Lớp chuỗi tiêu chuẩn duy nhất tôi biết trong C ++ trước đó là std :: string. Có một số hỗ trợ cho việc sử dụng char * là "chuỗi" để tương thích ngược với C, nhưng tôi gần như không bao giờ cần sử dụng nó. Chắc chắn, có rất nhiều lớp bên thứ ba do người dùng định nghĩa cho hầu hết mọi thứ bạn có thể tưởng tượng và các chuỗi có thể được bao gồm trong đó, nhưng tôi gần như không bao giờ phải sử dụng chúng.
TED

@TED: Chỉ vì bạn "hầu như không bao giờ phải sử dụng những thứ đó" không có nghĩa là người khác không thường xuyên sử dụng chúng. string_viewlà một loại lingua franca có thể làm việc với bất cứ điều gì.
Nicol Bolas

3
@TED: Đó là lý do tại sao tôi nói "C ++ là môi trường lập trình", trái ngược với "C ++ là ngôn ngữ / thư viện".
Nicol Bolas

2
@TED: " Vì vậy, tôi có thể nói" C ++ như một môi trường lập trình có hàng ngàn lớp container "? " Và nó cũng vậy. Nhưng tôi có thể viết các thuật toán hoạt động với các trình vòng lặp và bất kỳ lớp container nào tuân theo mô hình đó sẽ hoạt động với chúng. Ngược lại, "thuật toán" có thể lấy bất kỳ mảng ký tự liền kề nào khó viết hơn nhiều. Với string_view, thật dễ dàng.
Nicol Bolas

1
@TED: Mảng ký tự là một trường hợp rất đặc biệt. Chúng cực kỳ phổ biến và các thùng chứa các ký tự tiếp giáp khác nhau chỉ khác nhau về cách chúng quản lý bộ nhớ của chúng, không phải ở cách bạn lặp lại dữ liệu. Vì vậy, có một loại phạm vi lingua franca duy nhất có thể bao gồm tất cả các trường hợp như vậy mà không cần phải sử dụng một mẫu có ý nghĩa. Tổng quát hóa ngoài phạm vi này là tỉnh của Range TS và các mẫu.
Nicol Bolas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.