Ai đã thiết kế / thiết kế IOStreams của C ++ và nó vẫn được coi là được thiết kế tốt theo tiêu chuẩn ngày nay? [đóng cửa]


127

Trước hết, có vẻ như tôi đang hỏi ý kiến ​​chủ quan, nhưng đó không phải là những gì tôi theo đuổi. Tôi rất thích nghe một số lập luận có căn cứ về chủ đề này.


Với hy vọng có được cái nhìn sâu sắc về cách thiết kế một luồng / tuần tự hóa hiện đại nên được thiết kế, gần đây tôi đã có cho mình một bản sao của cuốn sách Standard C ++ IOStreams và Locales của Angelika Langer và Klaus Kreft . Tôi hình dung rằng nếu IOStreams không được thiết kế tốt, thì nó sẽ không đưa nó vào thư viện chuẩn C ++ ngay từ đầu.

Sau khi đọc các phần khác nhau của cuốn sách này, tôi bắt đầu nghi ngờ liệu IOStream có thể so sánh với ví dụ STL theo quan điểm kiến ​​trúc tổng thể hay không. Đọc ví dụ cuộc phỏng vấn này với Alexander Stepanov ("nhà phát minh" của STL) để tìm hiểu về một số quyết định thiết kế đã đi vào STL.

Điều làm tôi ngạc nhiên đặc biệt :

  • Dường như chưa biết ai chịu trách nhiệm cho thiết kế tổng thể của IOStreams (tôi rất muốn đọc một số thông tin cơ bản về điều này - có ai biết tài nguyên tốt không?);

  • Khi bạn đi sâu vào bên dưới bề mặt ngay lập tức của IOStreams, ví dụ: nếu bạn muốn mở rộng IOStream với các lớp của riêng mình, bạn có thể đến một giao diện với các tên hàm thành viên khá khó hiểu và khó hiểu, ví dụ getloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(và có thậm chí có thể là ví dụ tồi tệ hơn). Điều này làm cho nó trở nên khó khăn hơn nhiều để hiểu thiết kế tổng thể và cách các bộ phận đơn lẻ hợp tác. Ngay cả cuốn sách tôi đã đề cập ở trên cũng không giúp được nhiều (IMHO).


Vì vậy, câu hỏi của tôi:

Nếu bạn phải đánh giá theo các tiêu chuẩn kỹ thuật phần mềm ngày nay (nếu thực sự bất kỳ thỏa thuận chung nào về những điều này), liệu IOStream của C ++ có còn được coi là được thiết kế tốt không? (Tôi sẽ không muốn cải thiện kỹ năng thiết kế phần mềm của mình từ một thứ thường bị coi là lỗi thời.)


7
Thú vị ý kiến của Herb Sutter stackoverflow.com/questions/2485963/... :) Quá xấu mà chàng rời SO chỉ sau một vài ngày kể từ khi tham gia
Johannes Schaub - litb

5
Có ai khác nhìn thấy sự pha trộn các mối quan tâm trong các luồng STL không? Một luồng thường được thiết kế để đọc hoặc ghi byte và không có gì khác. Một thứ có thể đọc hoặc ghi các kiểu dữ liệu cụ thể là một trình định dạng (có thể không cần sử dụng một luồng để đọc / ghi các byte được định dạng). Trộn cả hai vào một lớp làm cho việc thực hiện các luồng riêng trở nên phức tạp hơn.
mmmmmmmm

4
@rsteven, có một sự tách biệt của những mối quan tâm đó. std::streambuflà lớp cơ sở để đọc và ghi byte và istream/ ostreamđược định dạng trong và đầu ra, lấy một con trỏ std::streambuflàm đích / nguồn của nó.
Julian Schaub - litb

1
@litb: Nhưng có thể chuyển đổi streambuf được sử dụng bởi luồng (định dạng) không? Vì vậy, có lẽ tôi muốn sử dụng định dạng STL nhưng muốn ghi dữ liệu qua một streambuf cụ thể?
mmmmmmmm

2
@rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Câu trả lời:


31

Một số ý tưởng vô hình thành tìm thấy con đường của họ vào tiêu chuẩn: auto_ptr, vector<bool>, valarrayexport, chỉ cần đến tên một vài. Vì vậy, tôi sẽ không coi sự hiện diện của IOStreams là một dấu hiệu của thiết kế chất lượng.

IOStreams có một lịch sử rô. Chúng thực sự là một bản làm lại của một thư viện stream trước đó, nhưng được tạo ra vào thời điểm mà nhiều thành ngữ C ++ ngày nay không tồn tại, vì vậy các nhà thiết kế không có lợi ích gì cho việc nhận thức muộn. Một vấn đề chỉ trở nên rõ ràng theo thời gian là gần như không thể thực hiện IOStream một cách hiệu quả như stdio của C, do việc sử dụng nhiều chức năng ảo và chuyển tiếp đến các đối tượng bộ đệm bên trong ở mức độ chi tiết tốt nhất và cũng nhờ vào sự kỳ lạ khó hiểu theo cách địa phương được xác định và thực hiện. Ký ức của tôi về điều này khá mờ nhạt, tôi sẽ thừa nhận; Tôi nhớ nó là chủ đề của cuộc tranh luận gay gắt vài năm trước, trên comp.lang.c ++. Được kiểm duyệt.


3
Cảm ơn về thông tin bạn vừa nhập. Tôi sẽ duyệt qua comp.lang.c++.moderatedkho lưu trữ và gửi liên kết ở cuối câu hỏi của tôi nếu tôi tìm thấy thứ gì đó có giá trị. - Bên cạnh đó, tôi dám không đồng ý với bạn về auto_ptr: Sau khi đọc C ++ của Herb Sutter, nó có vẻ như là một lớp rất hữu ích khi thực hiện mô hình RAII.
stakx - không còn đóng góp vào

5
@stakx: Tuy nhiên, nó đang bị phản đối và thay thế bởi unique_ptrngữ nghĩa rõ ràng và mạnh mẽ hơn.
ChúBens

3
@UncleBens unique_ptryêu cầu tham chiếu giá trị. Vì vậy, tại thời điểm auto_ptrnày là con trỏ rất mạnh mẽ.
Artyom

7
Nhưng auto_ptrđã vặn vẹo ngữ nghĩa sao chép / chuyển nhượng khiến nó trở thành một nơi thích hợp cho các lỗi hội thảo ...
Matthieu M.

5
@TokenMacGuy: nó không phải là một vectơ và nó không lưu trữ các phân. Mà làm cho nó hơi sai lệch. ;)
jalf

40

Về người đã thiết kế chúng, thư viện ban đầu (không đáng ngạc nhiên) được tạo ra bởi Bjarne Stroustrup, và sau đó được Dave Presotto triển khai lại. Điều này sau đó đã được Jerry Schwarz thiết kế lại và thực hiện lại một lần nữa cho Cfront 2.0, sử dụng ý tưởng của những kẻ thao túng từ Andrew Koenig. Phiên bản tiêu chuẩn của thư viện dựa trên việc thực hiện này.

Nguồn "Thiết kế và tiến hóa của C ++", phần 8.3.1.


3
@Neil - nut ý kiến ​​của bạn về thiết kế là gì? Dựa trên các câu trả lời khác của bạn, nhiều người rất thích nghe ý kiến ​​của bạn ...
DVK

1
@DVK Chỉ cần đăng ý kiến ​​của tôi như một câu trả lời riêng biệt.

2
Chỉ cần tìm thấy bản sao của một cuộc phỏng vấn với Bjarne Stroustrup, nơi anh ta đề cập đến một số mẩu và lịch sử của IOStreams: www2.research.att.com/~bs/01chinese.html (liên kết này dường như tạm thời bị phá vỡ ngay bây giờ, nhưng bạn có thể thử Bộ nhớ cache trang của Google)
stakx - không còn đóng góp vào

2
Liên kết cập nhật: stroustrup.com/01chinese.html .
FrankHB

28

Nếu bạn phải đánh giá theo các tiêu chuẩn kỹ thuật phần mềm ngày nay (nếu thực sự có bất kỳ thỏa thuận chung nào về những điều này), liệu IOStream của C ++ có còn được coi là được thiết kế tốt không? (Tôi sẽ không muốn cải thiện kỹ năng thiết kế phần mềm của mình từ một thứ thường bị coi là lỗi thời.)

Tôi sẽ nói KHÔNG , vì nhiều lý do:

Xử lý lỗi kém

Các điều kiện lỗi phải được báo cáo với các ngoại lệ, không phải với operator void*.

Mô hình chống "đối tượng zombie" là nguyên nhân gây ra lỗi như thế này .

Phân tách kém giữa định dạng và I / O

Điều này làm cho các đối tượng truyền phát phức tạp không cần thiết, vì chúng phải chứa thêm thông tin trạng thái để định dạng, cho dù bạn có cần hay không.

Nó cũng làm tăng tỷ lệ viết lỗi như:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Nếu thay vào đó, bạn đã viết một cái gì đó như:

cout << pad(to_hex(x), 8, '0') << endl;

Sẽ không có bit trạng thái liên quan đến định dạng và không có vấn đề gì.

Lưu ý rằng trong các ngôn ngữ "hiện đại" như Java, C #, và Python, tất cả các đối tượng có một toString/ ToString/ __str__chức năng đó được gọi bởi thói quen I / O. AFAIK, chỉ có C ++ thực hiện theo cách khác bằng cách sử dụng stringstreamlàm cách chuyển đổi tiêu chuẩn thành chuỗi.

Hỗ trợ kém cho i18n

Đầu ra dựa trên Iostream chia chuỗi ký tự chuỗi thành từng mảnh.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Chuỗi định dạng đặt toàn bộ câu thành chuỗi ký tự.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

Cách tiếp cận thứ hai dễ thích nghi hơn với các thư viện quốc tế hóa như GNU gettext, bởi vì việc sử dụng toàn bộ câu cung cấp nhiều ngữ cảnh hơn cho người dịch. Nếu thói quen định dạng chuỗi của bạn hỗ trợ sắp xếp lại (như các $tham số printf POSIX ), thì nó cũng xử lý tốt hơn các khác biệt về thứ tự từ giữa các ngôn ngữ.


4
Trên thực tế, đối với i18n, các thay thế phải được xác định theo vị trí (% 1,% 2, ..), vì một bản dịch có thể yêu cầu thay đổi thứ tự tham số. Mặt khác, tôi hoàn toàn đồng ý - +1.
peterchen

4
@peterchen: Đó là những gì các nhà đầu cơ POSIX $dành cho printf.
jamesdlin

2
Vấn đề không phải là định dạng chuỗi, đó là C ++ có các biến không an toàn kiểu.
dan04

5
Kể từ C ++ 11, giờ đây nó có các varargs an toàn.
Vịt Mooing

2
IMHO 'thông tin nhà nước thêm' là vấn đề tồi tệ nhất. cout là một toàn cầu; việc gắn các cờ định dạng vào nó làm cho các cờ đó trở nên toàn cầu và khi bạn cho rằng hầu hết việc sử dụng chúng đều có phạm vi dự định của một vài dòng, điều đó thật kinh khủng. Có thể khắc phục điều đó với lớp 'định dạng', liên kết với một khung hình nhưng vẫn giữ trạng thái riêng. Và, mọi thứ được thực hiện với cout nhìn chung rất tệ so với điều tương tự được thực hiện với printf (khi đó là có thể) ..
greggo

17

Tôi đang đăng bài này như một câu trả lời riêng biệt bởi vì đó là ý kiến ​​thuần túy.

Thực hiện đầu vào và đầu ra (đặc biệt là đầu vào) là một vấn đề rất, rất khó khăn, vì vậy không có gì đáng ngạc nhiên khi thư viện iostreams có đầy đủ các bộ phận và những điều mà với tầm nhìn hoàn hảo có thể được thực hiện tốt hơn. Nhưng dường như tất cả các thư viện I / O, trong bất kỳ ngôn ngữ nào đều như thế này. Tôi chưa bao giờ sử dụng ngôn ngữ lập trình trong đó hệ thống I / O là một thứ đẹp đẽ khiến tôi phải kinh ngạc trước nhà thiết kế của nó. Thư viện iostreams có những lợi thế, đặc biệt là thư viện CI / O (khả năng mở rộng, an toàn loại, v.v.), nhưng tôi không nghĩ có ai đang giữ nó như một ví dụ về OO hay thiết kế chung chung.


16

Ý kiến ​​của tôi về Cost iostreams đã được cải thiện đáng kể theo thời gian, đặc biệt là sau khi tôi bắt đầu thực sự mở rộng chúng bằng cách triển khai các lớp stream của riêng mình. Tôi bắt đầu đánh giá cao khả năng mở rộng và thiết kế tổng thể, mặc dù tên hàm thành viên nghèo một cách lố bịch như thế nào xsputnhoặc bất cứ điều gì. Bất kể, tôi nghĩ rằng luồng I / O là một cải tiến lớn so với C stdio.h, không có loại an toàn và bị đánh cắp bởi các lỗ hổng bảo mật lớn.

Tôi nghĩ vấn đề chính với các luồng IO là chúng kết hợp hai khái niệm liên quan nhưng hơi trực giao: định dạng văn bản và tuần tự hóa. Một mặt, các luồng IO được thiết kế để tạo ra một biểu diễn văn bản có thể đọc được, có thể định dạng của một đối tượng và mặt khác, để tuần tự hóa một đối tượng thành định dạng di động. Đôi khi hai mục tiêu này là một và giống nhau, nhưng lần khác, điều này dẫn đến một số sự không thống nhất gây khó chịu nghiêm trọng. Ví dụ:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Ở đây, những gì chúng ta nhận được làm đầu vào không phải là những gì chúng ta ban đầu xuất ra cho luồng. Điều này là do <<toán tử xuất ra toàn bộ chuỗi, trong khi đó >>toán tử sẽ chỉ đọc từ luồng cho đến khi gặp ký tự khoảng trắng, vì không có thông tin độ dài được lưu trữ trong luồng. Vì vậy, mặc dù chúng tôi xuất ra một đối tượng chuỗi có chứa "hello world", chúng tôi sẽ chỉ nhập một đối tượng chuỗi có chứa "hello". Vì vậy, trong khi luồng đã phục vụ mục đích của nó như là một cơ sở định dạng, nó đã thất bại trong việc tuần tự hóa đúng cách và sau đó hủy xác định đối tượng.

Bạn có thể nói rằng các luồng IO không được thiết kế để trở thành các cơ sở tuần tự hóa, nhưng nếu đó là trường hợp, các luồng đầu vào thực sự để làm gì? Ngoài ra, trong thực tế, các luồng I / O thường được sử dụng để tuần tự hóa các đối tượng, bởi vì không có các phương tiện tuần tự hóa tiêu chuẩn nào khác. Hãy xem xét boost::date_timehoặc boost::numeric::ublas::matrix, nếu bạn xuất một đối tượng ma trận với <<toán tử, bạn sẽ nhận được cùng một ma trận chính xác khi bạn nhập nó bằng >>toán tử. Nhưng để thực hiện điều này, các nhà thiết kế Boost phải lưu trữ thông tin về số lượng cột và hàng dưới dạng dữ liệu văn bản trong đầu ra, điều này làm ảnh hưởng đến màn hình thực tế có thể đọc được của con người. Một lần nữa, một sự kết hợp vụng về của các cơ sở định dạng văn bản và tuần tự hóa.

Lưu ý cách hầu hết các ngôn ngữ khác tách hai cơ sở này. Trong Java, ví dụ, định dạng được thực hiện thông qua toString()phương thức, trong khi tuần tự hóa được thực hiện thông qua Serializablegiao diện.

Theo tôi, giải pháp tốt nhất có thể là giới thiệu các luồng dựa trên byte , bên cạnh các luồng dựa trên ký tự chuẩn . Các luồng này sẽ hoạt động trên dữ liệu nhị phân, không liên quan đến định dạng / hiển thị có thể đọc được của con người. Chúng chỉ có thể được sử dụng như các phương tiện tuần tự hóa / giải tuần tự hóa, để dịch các đối tượng C ++ thành các chuỗi byte di động.


cảm ơn vì đã trả lời Tôi cũng có thể sai về điều này, nhưng liên quan đến điểm cuối cùng của bạn (luồng dựa trên byte so với luồng dựa trên ký tự), không phải là câu trả lời (một phần?) Của IOStream cho việc phân tách giữa bộ đệm luồng (chuyển đổi ký tự, truyền tải và đệm) và luồng (định dạng / phân tích cú pháp)? Và bạn không thể tạo các lớp luồng mới, các lớp chỉ dành cho tuần tự hóa và giải tuần tự hóa (có thể đọc bằng máy) và các lớp khác được định hướng duy nhất theo định dạng & phân tích cú pháp (có thể đọc được)?
stakx - không còn đóng góp

@stakx, vâng, và trên thực tế, tôi đã làm điều này. Nó hơi khó chịu hơn một chút, vì std::char_traitskhông thể chuyên môn hóa được unsigned char. Tuy nhiên, có cách giải quyết, vì vậy tôi đoán khả năng mở rộng đến giải cứu một lần nữa. Nhưng tôi nghĩ rằng thực tế là các luồng dựa trên byte không phải là tiêu chuẩn của thư viện.
Charles Salvia

4
Ngoài ra, việc triển khai các luồng nhị phân yêu cầu bạn triển khai các lớp luồng mới các lớp bộ đệm mới, vì các mối quan tâm định dạng không hoàn toàn tách biệt với std::streambuf. Vì vậy, về cơ bản, điều duy nhất bạn mở rộng là std::basic_ioslớp học. Vì vậy, có một dòng trong đó "mở rộng" đi qua lãnh thổ "thực hiện lại hoàn toàn" và tạo ra một luồng nhị phân từ các cơ sở luồng I / O của C ++ dường như tiếp cận điểm đó.
Charles Salvia

cũng nói và chính xác những gì tôi nghi ngờ. Và thực tế là cả C và C ++ đều có những bước tiến dài để không đảm bảo về độ rộng bit và biểu diễn cụ thể thực sự có thể trở thành vấn đề khi thực hiện I / O.
stakx - không còn đóng góp

" Để tuần tự hóa một đối tượng thành một định dạng di động. " Không, họ không bao giờ có ý định hỗ trợ điều đó
tò mò

11

tôi luôn thấy C ++ IOStreams không được thiết kế: việc triển khai chúng khiến cho việc xác định đúng một loại luồng mới là rất khó. họ cũng trộn lẫn các tính năng io và các tính năng định dạng (nghĩ về các trình thao tác).

cá nhân, thiết kế và triển khai luồng tốt nhất tôi từng thấy nằm ở ngôn ngữ lập trình Ada. nó là một mô hình trong việc tách rời, một niềm vui để tạo ra loại luồng mới và các hàm đầu ra luôn hoạt động bất kể luồng được sử dụng. đây là nhờ một mẫu số ít phổ biến nhất: bạn xuất byte cho một luồng và đó là nó. Các hàm truyền phát đảm nhiệm việc đưa các byte vào luồng, công việc của chúng không phải là định dạng một số nguyên thành thập lục phân (tất nhiên, có một tập các thuộc tính loại, tương đương với một thành viên lớp, được xác định để xử lý định dạng)

tôi ước C ++ đơn giản như liên quan đến các luồng ...


Cuốn sách tôi đã đề cập giải thích kiến ​​trúc IOStreams cơ bản như sau: Có một lớp vận chuyển (các lớp bộ đệm luồng) và một lớp phân tích / định dạng (các lớp luồng). Cái trước chịu trách nhiệm đọc / viết các ký tự từ / đến một bytestream, trong khi cái sau chịu trách nhiệm phân tích các ký tự hoặc tuần tự hóa các giá trị thành các ký tự. Điều này có vẻ đủ rõ ràng, nhưng tôi không chắc liệu những mối quan tâm này có thực sự tách biệt rõ ràng trong thực tế hay không, đặc biệt. khi địa phương đi vào chơi - Tôi cũng đồng ý với bạn về những khó khăn khi triển khai các lớp stream mới.
stakx - không còn đóng góp vào

"Trộn các tính năng io và các tính năng định dạng" <- Điều gì sai về điều đó? Đó là loại điểm của thư viện. Liên quan đến việc tạo luồng mới, bạn nên tạo luồng thay vì luồng và tạo luồng đơn giản xung quanh luồng.
Billy ONeal

Dường như câu trả lời cho câu hỏi này khiến tôi hiểu một điều mà tôi chưa bao giờ được giải thích: tôi nên lấy một streambuf thay vì stream ...
Adrien Plisson

@stakx: Nếu lớp streambuf đã làm những gì bạn nói, nó sẽ ổn thôi. Nhưng việc chuyển đổi giữa chuỗi ký tự và byte đều được trộn lẫn với I / O thực tế (tệp, bàn điều khiển, v.v.). Không có cách nào để thực hiện I / O tệp mà không thực hiện chuyển đổi ký tự, điều này rất đáng tiếc.
Ben Voigt

10

Tôi nghĩ rằng thiết kế IOStreams là tuyệt vời về khả năng mở rộng và hữu ích.

  1. Bộ đệm luồng: hãy xem các phần mở rộng boost.iostream: tạo gzip, tee, sao chép luồng trong vài dòng, tạo bộ lọc đặc biệt, v.v. Nó sẽ không thể có được nếu không có nó.
  2. Tích hợp nội địa hóa và định dạng tích hợp. Xem những gì có thể được thực hiện:

    std::cout << as::spellout << 100 << std::endl;

    Có thể in: "một trăm" hoặc thậm chí:

    std::cout << translate("Good morning")  << std::endl;

    Có thể in "Bonjour" hoặc "TOUR ט" theo địa phương thấm nhuần std::cout!

    Những điều như vậy có thể được thực hiện chỉ vì iostreams rất linh hoạt.

Nó có thể được thực hiện tốt hơn?

Tất nhiên là có thể! Trên thực tế, có rất nhiều điều có thể được cải thiện ...

Ngày nay khá khó để lấy chính xác từ stream_bufferđó, việc thêm thông tin định dạng bổ sung vào luồng là khá khó khăn, nhưng có thể.

Nhưng nhìn lại nhiều năm trước tôi vẫn thiết kế thư viện đủ tốt để sắp mang lại nhiều điều tốt đẹp.

Bởi vì bạn không thể luôn nhìn thấy bức tranh lớn, nhưng nếu bạn để lại điểm cho các tiện ích mở rộng, nó mang lại cho bạn khả năng tốt hơn nhiều ngay cả ở những điểm bạn không nghĩ tới.


5
Bạn có thể đưa ra nhận xét về lý do tại sao các ví dụ của bạn cho điểm 2 sẽ tốt hơn là chỉ đơn giản sử dụng một cái gì đó giống như print (spellout(100));print (translate("Good morning"));Điều này có vẻ như là một ý tưởng tốt, vì điều này tách rời định dạng và i18n từ I / O.
SCHEDLER

3
Bởi vì nó có thể được dịch theo langauage thấm nhuần vào luồng. tức là : french_output << translate("Good morning"); english_output << translate("Good morning") sẽ cung cấp cho bạn: "Bonjour Chào buổi sáng"
Artyom

3
Bản địa hóa khó hơn nhiều khi bạn cần thực hiện '<< "văn bản" << giá trị' bằng một ngôn ngữ nhưng '<< giá trị << "văn bản"' bằng ngôn ngữ khác - so với printf
Martin Beckett

@Martin Beckett Tôi biết, hãy xem thư viện Boost.Locale, điều gì xảy ra trong trường hợp như vậy bạn làm out << format("text {1}") % valuevà nó có thể được dịch sang "{1} translated". Vì vậy, nó hoạt động tốt ;-).
Artyom

15
Những gì "có thể được thực hiện" không liên quan lắm. Bạn là một lập trình viên, mọi thứ đều có thể được thực hiện với đủ nỗ lực. Nhưng IOStreams làm cho nó cực kỳ đau đớn để đạt được hầu hết những gì có thể được thực hiện . Và bạn thường nhận được hiệu suất tệ hại cho rắc rối của bạn.
jalf

2

(Câu trả lời này chỉ dựa trên ý kiến ​​của tôi)

Tôi nghĩ rằng IOStreams phức tạp hơn nhiều so với chức năng tương đương của chúng. Khi tôi viết bằng C ++, tôi vẫn sử dụng các tiêu đề cstdio cho I / O "kiểu cũ", điều mà tôi thấy dễ đoán hơn nhiều. Mặt khác, (mặc dù nó không thực sự quan trọng; chênh lệch thời gian tuyệt đối là không đáng kể) Các iOStream đã được chứng minh trong nhiều trường hợp chậm hơn CI / O.


Tôi nghĩ bạn có nghĩa là "chức năng" chứ không phải "chức năng". lập trình chức năng tạo ra mã mà thậm chí còn tệ hơn khi lập trình chung đó.
Chris Becke

Cảm ơn đã chỉ ra sai lầm đó; Tôi đã chỉnh sửa câu trả lời để phản ánh sự điều chỉnh.
Delan Azabani

5
IOStreams gần như chắc chắn sẽ phải chậm hơn so với stdio cổ điển; nếu tôi được giao nhiệm vụ thiết kế khung luồng I / O có thể mở rộng và dễ sử dụng, tôi có thể sẽ phán đoán tốc độ thứ cấp, do các tắc nghẽn thực sự có thể sẽ là tốc độ I / O hoặc băng thông lưu lượng mạng.
stakx - không còn đóng góp vào

1
Tôi đồng ý rằng đối với I / O hoặc mạng, tốc độ tính toán không quan trọng lắm. Tuy nhiên, hãy nhớ rằng C ++ để chuyển đổi số / chuỗi đang sử dụng sstringstream. Tôi nghĩ tốc độ không thành vấn đề, mặc dù nó là thứ yếu.
Matthieu M.

1
@stakx tập tin I / O và tắc nghẽn mạng là một hàm của chi phí 'mỗi byte' khá nhỏ và bị giảm đáng kể bởi những cải tiến công nghệ. Ngoài ra, với DMA, các tổng phí này không lấy đi thời gian của CPU từ các luồng khác trên cùng một máy. Vì vậy, nếu bạn đang thực hiện định dạng đầu ra, chi phí để thực hiện điều đó một cách hiệu quả so với không, có thể dễ dàng đáng kể (ít nhất, không bị lu mờ bởi đĩa hoặc mạng; nhiều khả năng nó bị lu mờ bởi quá trình xử lý khác trong ứng dụng).
greggo

2

Tôi luôn gặp phải những bất ngờ khi sử dụng IOStream.

Các thư viện dường như văn bản định hướng và không định hướng nhị phân. Đó có thể là điều ngạc nhiên đầu tiên: sử dụng cờ nhị phân trong các luồng tệp không đủ để có hành vi nhị phân. Người dùng Charles Salvia ở trên đã quan sát chính xác: IOStreams trộn các khía cạnh định dạng (nơi bạn muốn đầu ra đẹp, ví dụ: chữ số giới hạn cho số float) với các khía cạnh tuần tự hóa (nơi bạn không muốn mất thông tin). Có lẽ sẽ tốt khi tách những khía cạnh này. Boost.Serialization làm nửa này. Bạn có một chức năng tuần tự hóa định tuyến đến các trình chèn và trích xuất nếu bạn muốn. Đã có bạn có sự căng thẳng giữa cả hai khía cạnh.

Nhiều hàm cũng có ngữ nghĩa khó hiểu (ví dụ: get, getline, bỏ qua và đọc. Một số trích xuất dấu phân cách, một số không; cũng có một số thiết lập eof). Hơn nữa trên một số đề cập đến các tên hàm kỳ lạ khi thực hiện một luồng (ví dụ: xsputn, uflow, underflow). Mọi thứ thậm chí còn tồi tệ hơn khi một người sử dụng các biến thể wchar_t. Wifstream thực hiện dịch sang multibyte trong khi wopesstream thì không. I / O nhị phân không hoạt động ngoài hộp với wchar_t: bạn có phần ghi đè codecvt.

I / O được đệm c (tức là TẬP_TIN) không mạnh bằng đối tác C ++ của nó, nhưng minh bạch hơn và có ít hành vi phản kháng trực quan hơn nhiều.

Tuy nhiên, mỗi khi tôi vấp phải IOStream, tôi bị thu hút bởi nó như một con thiêu thân đến lửa. Có lẽ sẽ là một điều tốt nếu một người thực sự thông minh sẽ có một cái nhìn tốt về kiến ​​trúc tổng thể.


1

Tôi không thể giúp trả lời phần đầu của câu hỏi (Ai đã làm điều đó?). Nhưng nó đã được trả lời trong các bài viết khác.

Đối với phần thứ hai của câu hỏi (Được thiết kế tốt?), Câu trả lời của tôi là một câu "Không!" Vang dội. Dưới đây là một ví dụ nhỏ khiến tôi lắc đầu không tin từ nhiều năm:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

Các mã trên tạo ra vô nghĩa do thiết kế iostream. Vì một số lý do ngoài tầm hiểu biết của tôi, họ coi byte uint8_t là ký tự, trong khi các loại tích phân lớn hơn được coi như số. Qed Thiết kế xấu.

Cũng không có cách nào tôi có thể nghĩ ra để khắc phục điều này. Thay vào đó, loại này cũng có thể là hình nổi hoặc hình đôi ... vì vậy, việc chọn một 'int' để làm cho iostream ngớ ngẩn hiểu rằng những con số không phải là ký tự là chủ đề sẽ không giúp ích gì.

Sau khi nhận được một phiếu bầu thấp cho câu trả lời của tôi, có thể thêm một vài lời giải thích ... Thiết kế IOStream còn thiếu sót vì nó không cung cấp cho người lập trình một phương tiện để nêu cách xử lý một mục. Việc triển khai IOStream đưa ra các quyết định tùy ý (chẳng hạn như coi uint8_t là char, không phải là số byte). Đây là một lỗ hổng trong thiết kế IOStream, khi họ cố gắng đạt được điều không thể đạt được.

C ++ không cho phép phân loại một loại - ngôn ngữ không có phương tiện. Không có thứ nào như is_number_type () hoặc is_character_type () IOStream có thể sử dụng để đưa ra lựa chọn tự động hợp lý. Bỏ qua điều đó và cố gắng thoát khỏi việc đoán IS là một lỗ hổng thiết kế của một thư viện.

Phải thừa nhận, printf () sẽ không hoạt động như nhau trong triển khai "ShowVector ()" chung. Nhưng đó không phải là lý do cho hành vi iostream. Nhưng rất có thể trong trường hợp printf (), ShowVector () sẽ được định nghĩa như thế này:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );

3
Đổ lỗi không (hoàn toàn) nằm với iostream. Kiểm tra xem bạn uint8_tlà một typedef để làm gì. Nó thực sự là một char? Sau đó, đừng đổ lỗi cho iostreams vì coi nó như một chiếc char.
Martin Ba

Và nếu bạn muốn đảm bảo rằng bạn nhận được một số trong mã chung, bạn có thể sử dụng num_putkhía cạnh thay vì toán tử chèn luồng.
Martin Ba

@Martin Ba Bạn đã đúng - Các tiêu chuẩn c / c ++ giữ cho nó mở có bao nhiêu byte mà một "int unsign ngắn" có. "unsign char" là một đặc điểm riêng của ngôn ngữ. Nếu bạn thực sự muốn một byte, bạn phải sử dụng một char không dấu. C ++ cũng không cho phép áp đặt các hạn chế đối với các đối số mẫu - chẳng hạn như "chỉ số" và vì vậy nếu tôi thay đổi việc triển khai ShowVector thành giải pháp num_put được đề xuất của bạn, ShowVector không thể hiển thị một vectơ chuỗi nữa, phải không? ;)
BitTickler

1
@Martin Bla: cppreference đề cập rằng int8_t là loại số nguyên có chữ ký với độ rộng chính xác là 8 bit. Tôi đồng ý với tác giả rằng thật lạ khi bạn nhận được đầu ra rác, mặc dù về mặt kỹ thuật có thể giải thích được bằng typedef và quá tải . Nó có thể đã được giải quyết bằng cách có __int8 một loại thực sự thay vì một typedef.
gast128

Ồ, nó thực sự khá dễ sửa: // Các bản sửa lỗi cho std :: Ostream đã bị hỏng hỗ trợ cho các loại không dấu / đã ký / char // và in các số nguyên 8 bit giống như chúng là các ký tự. không gian tên Ostream_fixes {inline std :: ostream & toán tử << (std :: ostream & os, unsign char i) {return os << static_cast <unsign int> (i); } inline std :: Ostream & toán tử << (std :: ostream & os, đã ký char i) {return os << static_cast <Sign int> (i); }} // không gian tên
Ostream_fixes

1

Các iostream của C ++ có rất nhiều sai sót, như đã lưu ý trong các phản hồi khác, nhưng tôi muốn lưu ý điều gì đó trong sự bảo vệ của nó.

C ++ hầu như là duy nhất trong số các ngôn ngữ được sử dụng nghiêm túc, giúp đầu vào và đầu ra thay đổi dễ dàng cho người mới bắt đầu. Trong các ngôn ngữ khác, đầu vào của người dùng có xu hướng liên quan đến kiểu ép buộc hoặc trình định dạng chuỗi, trong khi C ++ làm cho trình biên dịch thực hiện tất cả công việc. Điều tương tự cũng đúng với đầu ra, mặc dù C ++ không phải là duy nhất trong vấn đề này. Tuy nhiên, bạn có thể thực hiện I / O được định dạng khá tốt trong C ++ mà không cần phải hiểu các lớp và các khái niệm hướng đối tượng, rất hữu ích về mặt sư phạm và không cần phải hiểu cú pháp định dạng. Một lần nữa, nếu bạn đang dạy người mới bắt đầu, đó là một điểm cộng lớn.

Sự đơn giản này cho người mới bắt đầu có giá, có thể khiến bạn đau đầu khi xử lý I / O trong các tình huống phức tạp hơn, nhưng hy vọng rằng vào thời điểm đó, lập trình viên đã học đủ để có thể đối phó với chúng, hoặc ít nhất là đã đủ tuổi uống.

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.