Hiệu suất nào chúng ta có thể mong đợi từ std :: string's c_str ()? Luôn luôn thời gian không đổi?


13

Gần đây tôi đã thực hiện một số tối ưu hóa cần thiết. Một điều tôi đã làm là thay đổi một số dòng chảy -> chạy nước rút. Tôi đang chạy một loạt std :: chuỗi sang mảng kiểu ac, ala

char foo[500];
sprintf(foo, "%s+%s", str1.c_str(), str2.c_str());

Hóa ra việc triển khai std :: string :: c_str () của Microsoft chạy trong thời gian không đổi (nó chỉ trả về một con trỏ bên trong). Dường như libstdc ++ cũng làm như vậy . Tôi nhận ra std không đảm bảo cho c_str, nhưng thật khó để tưởng tượng một cách khác để làm điều này. Ví dụ, nếu họ sao chép vào bộ nhớ, họ sẽ phải phân bổ bộ nhớ cho bộ đệm (để lại cho người gọi để hủy nó - KHÔNG phải là một phần của hợp đồng STL) HOẶC họ phải sao chép vào tĩnh bên trong bộ đệm (có thể không phải là chủ đề an toàn và bạn không có gì đảm bảo về tuổi thọ của nó). Vì vậy, chỉ cần trả về một con trỏ đến một chuỗi kết thúc null được duy trì bên trong dường như là giải pháp thực tế duy nhất.

Câu trả lời:


9

Nếu tôi nhớ lại, tiêu chuẩn cho phép string::c_str()trả về khá nhiều thứ thỏa mãn:

  • Dung lượng đủ lớn cho nội dung của chuỗi và kết thúc NULL
  • Phải hợp lệ cho đến khi một thành viên không phải là thành viên của stringđối tượng đã cho được gọi

Vì vậy, trong thực tế, điều này có nghĩa là một con trỏ đến bộ nhớ trong; vì không có cách nào để theo dõi bên ngoài cuộc sống của con trỏ được trả về. Tôi nghĩ rằng tối ưu hóa của bạn là an toàn để giả định đây là (nhỏ) thời gian không đổi.

Trên một lưu ý liên quan, nếu định dạng chuỗi là giới hạn hiệu suất; bạn có thể thấy may mắn hơn khi trì hoãn việc đánh giá cho đến khi thực sự cần thiết với thứ gì đó như Boost.Phoenix .

Boost.Format Tôi tin rằng trì hoãn định dạng bên trong cho đến khi kết quả được yêu cầu và bạn có thể sử dụng cùng một đối tượng định dạng mà không cần phân tích lại chuỗi định dạng, điều mà tôi thấy có sự khác biệt đáng kể đối với ghi nhật ký tần số cao.


2
Có thể thực hiện để tạo một bộ đệm nội bộ mới hoặc thứ cấp - đủ lớn để thêm vào bộ kết thúc null. Mặc dù c_strlà một phương thức const (hoặc ít nhất là có quá tải const - tôi quên điều đó), nhưng điều này không thay đổi giá trị logic, vì vậy có thể là một lý do cho mutable. Nó sẽ ngắt các con trỏ khỏi các lệnh gọi khácc_str , ngoại trừ mọi con trỏ như vậy phải tham chiếu đến cùng một chuỗi logic (vì vậy không có lý do mới nào để tái phân bổ - phải có một dấu kết thúc null) hoặc nếu không thì phải có một lệnh gọi đến không phương pháp -const ở giữa.
Steve314

Nếu điều này thực sự hợp lệ, c_strcác cuộc gọi có thể là thời gian O (n) cho việc tái phân bổ và sao chép. Nhưng cũng có thể có các quy tắc bổ sung trong tiêu chuẩn mà tôi không biết điều đó sẽ ngăn chặn điều này. Lý do tôi đề nghị nó - các cuộc gọi đến c_strkhông thực sự có nghĩa là để được phổ biến AFAIK, vì vậy nó có thể không được coi là quan trọng để đảm bảo họ đang nhanh - tránh mà thêm byte dung lượng lưu trữ cho một null terminator thường không cần thiết trong stringtrường hợp mà không bao giờ sử dụng c_strcó thể đã được ưu tiên.
Steve314

Boost.Formatbên trong đi qua các luồng mà bên trong đi qua sprintfkết thúc với chi phí khá lớn. Các tài liệu nói rằng nó chậm hơn khoảng 8 lần so với đồng bằng sprintf. Nếu bạn muốn hiệu suất và an toàn loại, hãy thử Boost.Spirit.Karma.
Jan Hudec

Boost.Spirit.Karmalà một mẹo hay cho hiệu năng, nhưng hãy cẩn thận vì nó có một phương pháp rất khác nhau có thể khó để điều chỉnh printfmã kiểu hiện có (và bộ mã hóa). Tôi đã bị mắc kẹt nhiều Boost.Formatvì I / O của chúng tôi không đồng bộ; nhưng một yếu tố lớn là tôi có thể thuyết phục các đồng nghiệp của mình sử dụng nó một cách nhất quán (vẫn cho phép bất kỳ loại nào ostream<<bị quá tải - điều này làm cho .c_str()cuộc tranh luận diễn ra một cách độc đáo ) Các con số của Karma .
rvalue

23

Trong tiêu chuẩn c ++ 11 (Tôi đang đọc phiên bản N 3290), chương 21.4.7.1 nói về phương thức c_str ():

const charT* c_str() const noexcept; const charT* data() const noexcept;

Trả về: Một con trỏ p sao cho p + i == & toán tử cho mỗi i trong [0, size ()].
Độ phức tạp: thời gian không đổi.
Yêu cầu: Chương trình không được thay đổi bất kỳ giá trị nào được lưu trong mảng ký tự.

Vì vậy, có: độ phức tạp thời gian liên tục được đảm bảo bởi tiêu chuẩn.

Tôi chỉ kiểm tra tiêu chuẩn c ++ 03, và nó không có yêu cầu như vậy, cũng không nói lên sự phức tạp.


8

Trong lý thuyết C ++ 03 không yêu cầu điều đó, và do đó chuỗi có thể là một mảng char trong đó sự hiện diện của bộ kết thúc null được thêm vào tại thời điểm c_str () được gọi. Điều này có thể yêu cầu phân bổ lại (nó không vi phạm hằng số, nếu con trỏ riêng bên trong được khai báo là mutable).

C ++ 11 chặt chẽ hơn: nó đòi hỏi chi phí thời gian, do đó không thể di chuyển và mảng phải luôn đủ rộng để lưu trữ null ở cuối. c_str (), tự nó, vẫn có thể làm " ptr[size()]='\0'" để đảm bảo null thực sự có mặt. Nó không vi phạm hằng số của mảng vì phạm vi [0..size())không thay đổi.

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.