2009-07-19 36 views
7

Tôi đã viết phiên bản nhị phân của iostream. Về cơ bản nó cho phép bạn viết các tệp nhị phân, nhưng cho phép bạn kiểm soát nhiều định dạng tệp. Ví dụ sử dụng: (. Nơi tiền tố là u16le)Phiên bản nhị phân của iostream

my_file << binary::u32le << my_int << binary::u16le << my_string; 

Sẽ viết my_int như một số nguyên 32-bit unsigned, và my_string như là một chuỗi dài-tiền tố Để đọc các tập tin trở lại, bạn sẽ lật các mũi tên. Hoạt động tuyệt vời. Tuy nhiên, tôi đã va chạm vào thiết kế, và tôi vẫn đang ở trên hàng rào. Vì vậy, thời gian để hỏi SO. (Chúng tôi đưa ra một vài giả định, chẳng hạn như byte 8 bit, ints bổ sung 2 giây và IEEE nổi tại thời điểm này.)

iostreams, dưới mui xe, sử dụng streambufs. Đó là một thiết kế tuyệt vời thực sự - iostreams mã serialization của một 'int' vào văn bản, và để cho streambuf cơ bản xử lý phần còn lại. Vì vậy, bạn nhận được cout, fstreams, stringstreams, vv Tất cả những điều này, cả iostreams và streambufs, được templated, thường là trên char, nhưng đôi khi cũng là một wchar. Tuy nhiên, dữ liệu của tôi là luồng byte, được thể hiện tốt nhất bởi 'unsigned char'.

Lần thử đầu tiên của tôi là lấy mẫu các lớp dựa trên unsigned char. std::basic_string mẫu đủ tốt, nhưng streambuf thì không. Tôi gặp phải một số vấn đề với một lớp học có tên là codecvt, mà tôi không bao giờ có thể theo dõi chủ đề unsigned char. Điều này đặt ra hai câu hỏi:

1) Tại sao một streambuf chịu trách nhiệm cho những thứ như vậy? Nó có vẻ như chuyển đổi mã nằm cách ra khỏi trách nhiệm của một streambuf - streambufs nên có một dòng, và dữ liệu đệm đến/từ nó. Chỉ có bấy nhiêu thôi. Một cái gì đó ở mức cao như chuyển đổi mã cảm thấy như nó nên thuộc về iostreams.

Vì tôi không thể lấy các streambuf hoạt động với char chưa ký, tôi đã quay trở lại char và chỉ truyền dữ liệu giữa char/unsigned char. Tôi đã cố gắng giảm thiểu số lượng phôi, vì lý do rõ ràng. Hầu hết các dữ liệu về cơ bản gió lên trong một hàm read() hoặc write(), sau đó gọi streambuf cơ bản. (Và sử dụng một diễn viên trong quá trình.) Chức năng đọc về cơ bản là:

size_t read(unsigned char *buffer, size_t size) 
{ 
    size_t ret; 
    ret = stream()->sgetn(reinterpret_cast<char *>(buffer), size); 
    // deal with ret for return size, eof, errors, etc. 
    ... 
} 

Giải pháp tốt, giải pháp xấu?


Hai câu hỏi đầu tiên chỉ ra rằng cần thêm thông tin. Đầu tiên, các dự án như boost :: serialization được xem xét, nhưng chúng tồn tại ở mức cao hơn, ở chỗ chúng xác định định dạng nhị phân riêng của chúng. Điều này là nhiều hơn cho việc đọc/ghi ở mức độ thấp hơn, nơi mà nó muốn xác định định dạng, hoặc định dạng đã được xác định, hoặc siêu dữ liệu số lượng lớn là không cần thiết hoặc mong muốn.

Thứ hai, một số đã hỏi về công cụ sửa đổi binary::u32le. Nó là một sự khởi đầu của một lớp có độ dài mong muốn và độ rộng mong muốn, tại thời điểm này, có lẽ đã được ký kết trong tương lai. Luồng giữ một bản sao của thể hiện được truyền cuối cùng của lớp đó và sử dụng nó trong tuần tự hóa. Đây là một chút của một workaround, tôi orginally cố gắng quá tải toán tử < < thusly:

bostream &operator << (uint8_t n); 
bostream &operator << (uint16_t n); 
bostream &operator << (uint32_t n); 
bostream &operator << (uint64_t n); 

Tuy nhiên vào thời điểm đó, điều này dường như không làm việc. Tôi đã gặp một số vấn đề với cuộc gọi hàm mơ hồ. Điều này đặc biệt đúng với các hằng số, mặc dù bạn có thể, như một poster được đề xuất, bỏ hoặc chỉ đơn thuần khai báo nó như là một const <type>. Tôi dường như nhớ rằng có một số vấn đề lớn hơn tuy nhiên.

+0

Sự cố gọi hàm mơ hồ của bạn có thể do thực tế rằng [u] int32_t thường được định nghĩa là "[unsigned] long". Vì vậy, nếu bạn cố gắng viết "[unsigned] int", mà chữ số có xu hướng, quảng cáo là cần thiết, nhưng loại để quảng bá là mơ hồ (ví dụ: dài so với gấp đôi). –

+0

Bạn có thể xin vui lòng gửi đến đầu ra trình biên dịch trên những xung đột bạn đã có với unsigned char streambuf? – Basilevs

Trả lời

1

Như tôi đã hiểu, thuộc tính luồng mà bạn đang sử dụng để chỉ định loại sẽ phù hợp hơn để chỉ định giá trị cuối cùng, đóng gói hoặc các giá trị "siêu dữ liệu" khác. Việc xử lý các loại bản thân nên được thực hiện bởi trình biên dịch. Ít nhất, đó là cách STL dường như được thiết kế.

Nếu bạn sử dụng quá tải để tự động tách các loại, bạn sẽ cần phải xác định loại chỉ khi nó là khác nhau từ các loại hình tuyên bố của biến:

Stream& operator<<(int8_t); 
Stream& operator<<(uint8_t); 
Stream& operator<<(int16_t); 
Stream& operator<<(uint16_t); 
etc. 

uint32_t x; 
stream << x << (uint16_t)x; 

Reading loại khác so với kiểu tuyên bố sẽ một chút lộn xộn hơn. Nói chung, mặc dù, đọc hoặc viết từ các biến của một loại khác với loại đầu ra nên tránh, tôi nghĩ.

Tôi tin rằng phiên bản mặc định của std :: codecvt không có gì, trả về "noconv" cho mọi thứ. Nó chỉ thực sự làm bất cứ điều gì khi sử dụng các dòng ký tự "rộng". Bạn không thể thiết lập một định nghĩa tương tự cho codecvt? Nếu vì một lý do nào đó, không thể xác định codecvt no-op cho luồng của bạn, thì tôi không thấy bất kỳ vấn đề nào với giải pháp truyền của bạn, đặc biệt là vì nó được phân tách thành một vị trí.

Cuối cùng, bạn có chắc chắn sẽ không sử dụng tốt hơn một số mã tuần tự tiêu chuẩn, chẳng hạn như Boost, thay vì tự cuộn của riêng mình?

+1

Boost :: serialization nằm ở mức cao hơn một chút, và không thể được sử dụng để hỗ trợ đọc các giao thức nhị phân hiện có, ví dụ, hoặc kiểm soát hoàn toàn chi tiết. Đối với codecvt, tôi đã thực hiện một số stabs ban đầu tại văn bản một không-op cho char unsigned, nhưng đã không thành công. Đối với các định nghĩa, ban đầu tôi bắt đầu với những gì bạn có, nhưng gặp phải các vấn đề với các cuộc gọi hàm mơ hồ và chuyển sang giải pháp hiện tại. Tôi có thể thử lại, vì sử dụng loại sẽ tự nhiên hơn nhiều. – Thanatos

+0

Vâng, có vẻ như bạn biết bạn đang làm gì. Đó là tất cả những phản hồi mà tôi có. Nếu bạn muốn có một vết nứt khác ở vấn đề quá tải hoặc codecvt, tôi chắc chắn SO sẽ rất vui khi nhìn vào nó. –

+0

Tôi nghĩ rằng đây là con đường để đi. Có lẽ bạn nên cố gắng làm việc các lỗi 'gọi hàm mơ hồ'. Tôi không nghĩ rằng (đã không thử nó) rằng thiết kế quá tải là thiếu sót (tôi có thể được chứng minh sai) –

0

Chúng tôi cần làm điều gì đó tương tự như những gì bạn đang làm nhưng chúng tôi đã đi theo một con đường khác. Tôi quan tâm đến cách bạn đã xác định giao diện của bạn. Một phần của những gì tôi không biết làm thế nào bạn có thể xử lý là các thao tác bạn đã xác định (nhị phân :: u32le, binaryu16le).

Với basic_streams, trình điều khiển kiểm soát tất cả các thành phần sau sẽ được đọc/ghi, nhưng trong trường hợp của bạn, nó có thể không có ý nghĩa, vì kích thước (một phần thông tin thao tác) bị ảnh hưởng bởi biến được chuyển vào và ra ngoài.

binary_istream in; 
int i; 
int i2; 
short s; 
in >> binary::u16le >> i >> binary::u32le >> i2 >> s; 

Trong đoạn mã trên, nó có thể có ý nghĩa xác định rằng cho dù biến i là 32 bit (giả sử int là 32 bit), bạn muốn trích xuất từ ​​dòng serialized chỉ 16 bit, trong khi bạn muốn trích xuất các đầy đủ 32 bit vào i2. Sau đó, người dùng buộc phải giới thiệu các thao tác cho mỗi loại khác được truyền vào, hoặc người thao túng khác vẫn có hiệu lực và khi đoạn mã ngắn được truyền vào và 32 bit được đọc với tràn có thể và theo bất kỳ cách nào người dùng có thể sẽ nhận được kết quả không mong muốn.

Kích thước dường như không thuộc về (theo ý kiến ​​của tôi) đối với người thao túng.

Cũng giống như một lưu ý phụ, chúng tôi đã có các ràng buộc khác như định nghĩa thời gian chạy các loại, và chúng tôi đã xây dựng hệ thống kiểu meta của riêng chúng tôi để xây dựng các kiểu trong thời gian chạy (một loại biến thể) và sau đó chúng tôi kết thúc việc triển khai de/serialization cho các kiểu (kiểu tăng cường), vì vậy các serializers của chúng ta không làm việc với các kiểu C++ cơ bản, mà là với các cặp tuần tự/dữ liệu.

+0

Tôi đã thực hiện một số chỉnh sửa cho câu hỏi liên quan đến câu trả lời của bạn. Bạn và các poster khác bây giờ có tôi lại suy nghĩ của tôi sử dụng các thao tác (tên tốt, họ cần một ...). Tôi cảm thấy có vấn đề với các cuộc gọi hàm mơ hồ. Điều này là đặc biệt. đúng trong << 6, mặc dù điều này có thể được thực hiện với trong << uint16_t (6). Các thao tác vẫn tồn tại, và nó là một lỗi để cố gắng đọc vào một biến 16bit với một trình xử lý 32 bit hiện diện. Tôi sẽ suy nghĩ về việc sử dụng này, tuy nhiên, và xem liệu có lẽ mô hình được mô tả bởi hai bạn là một sự phù hợp hơn. – Thanatos

+0

Tên không phải của tôi, mà là tiêu chuẩn. Trong tiêu chuẩn C++, chương 27.6 có tiêu đề: 'Định dạng và thao tác', và không giống như phiên bản của bạn, chúng không được thực hiện như các đối tượng được truyền vào luồng mà là các hàm miễn phí (templated) được thực thi bên trong luồng (ios_base hoặc basic_ios <>). Trong mỗi trường hợp chúng lấy và trả về các tham chiếu đến kiểu đã cho (basic_stream <>, basic_ios <> hoặc ios_base) –

0

Tôi sẽ không sử dụng toán tử < < vì liên kết quá mật thiết với định dạng văn bản I/O.

Tôi sẽ không sử dụng quá tải nhà điều hành cho thực tế. Tôi muốn tìm một thành ngữ khác.

+1

Tôi xem xét việc sử dụng nó chỉ trong chuyển động của mọi thứ đến/từ một luồng, nhưng có lẽ tôi thấy quan điểm của bạn. Tuy nhiên, nó chỉ là một cuộc gọi hàm - nó có thể dễ dàng được thay thế bằng .read (...) hoặc .write (...), nhưng sau đó bạn sẽ kết thúc với: stream.read (x) .read (y) .read (z) có thể có hoặc không có ý nghĩa. Tuy nhiên, vì hệ thống phân cấp lớp phù hợp với phân cấp của iostream, tại sao API không phải là quá? – Thanatos

+0

Thư viện chuẩn đã có các phương thức có thể đọc và ghi dữ liệu nhị phân. Chúng là các phương pháp, không phải là quá tải của << or >>.Lý do tôi đề xuất không sử dụng << and >> cho nhị phân I/O là mọi người nghĩ rằng được định dạng đầu vào và đầu ra khi họ nhìn thấy các toán tử đó. Định dạng ngụ ý những thứ như miền địa phương, biện minh trường, v.v. Bạn không thực hiện bất kỳ điều gì với I/O nhị phân, đó là lý do thư viện chuẩn cung cấp phương thức ghi basic_istream :: read và basic_ostream ::. – legalize

+0

Bạn _are_ làm hầu hết điều đó với I/O nhị phân. locale lưu giữ các định dạng endiannes hoặc floating point, sự biện minh của trường được thay thế bằng sự liên kết. Đầu ra được định dạng nhị phân giống với đầu ra văn bản. – Basilevs

2

Tôi đồng ý hợp pháp hóa. Tôi cần phải làm gần như chính xác những gì bạn đang làm, và nhìn quá tải <</>>, nhưng đến kết luận rằng iostream không được thiết kế để chứa nó.Đối với một điều, tôi không muốn phải phân lớp các lớp dòng để có thể xác định tình trạng quá tải của tôi.

Giải pháp của tôi (mà chỉ cần thiết để serialize dữ liệu tạm thời trên một máy duy nhất, và do đó không cần phải giải quyết endianness) được dựa trên mô hình này:

// deducible template argument read 
template <class T> 
void read_raw(std::istream& stream, T& value, 
    typename boost::enable_if< boost::is_pod<T> >::type* dummy = 0) 
{ 
    stream.read(reinterpret_cast<char*>(&value), sizeof(value)); 
} 

// explicit template argument read 
template <class T> 
T read_raw(std::istream& stream) 
{ 
    T value; 
    read_raw(stream, value); 
    return value; 
} 

template <class T> 
void write_raw(std::ostream& stream, const T& value, 
    typename boost::enable_if< boost::is_pod<T> >::type* dummy = 0) 
{ 
    stream.write(reinterpret_cast<const char*>(&value), sizeof(value)); 
} 

tôi sau đó tiếp tục quá tải read_raw/write_raw cho bất kỳ các loại không thuộc POD (ví dụ: dây). Lưu ý rằng chỉ có phiên bản đầu tiên của read_raw cần được quá tải; nếu bạn use ADL correctly, phiên bản thứ hai (1-arg) có thể gọi quá tải 2 arg được xác định sau và trong các không gian tên khác.

Viết dụ:

int32_t x; 
int64_t y; 
int8_t z; 
write_raw(is, x); 
write_raw(is, y); 
write_raw<int16_t>(is, z); // explicitly write int8_t as int16_t 

đọc ví dụ:

int32_t x = read_raw<int32_t>(is); // explicit form 
int64_t y; 
read_raw(is, y); // implicit form 
int8_t z = numeric_cast<int8_t>(read_raw<int16_t>(is)); 

Nó không phải là sexy như khai thác quá tải, và những thứ không phù hợp trên cùng một dòng một cách dễ dàng (mà tôi có xu hướng tránh nào , vì các điểm ngắt lỗi là định hướng dòng), nhưng tôi nghĩ nó đã trở nên đơn giản hơn, rõ ràng hơn và không tiết lộ nhiều hơn.

Các vấn đề liên quan