2009-10-13 1 views
50

Обычно я использую stringstream для записи в строку в памяти. Есть ли способ записи в буфер символов в двоичном режиме? Рассмотрим следующий код:Есть ли двоичные потоки памяти в C++

stringstream s; 
s << 1 << 2 << 3; 
const char* ch = s.str().c_str(); 

Память на ch будет выглядеть следующим образом: 0x313233 - в ASCII кодов символов 1, 2 и 3. Я ищу способ, чтобы написать сами двоичные значения. То есть, я хочу 0x010203 в памяти. Проблема в том, что я хочу, чтобы у меня была возможность написать функцию

void f(ostream& os) 
{ 
    os << 1 << 2 << 3; 
} 

И решить, какой поток использовать. Что-то вроде этого:

mycharstream c; 
c << 1 << 2 << 3; // c.data == 0x313233; 
mybinstream b; 
b << 1 << 2 << 3; // b.data == 0x010203; 

Любые идеи?

+1

Это шестнадцатеричный, а не двоичный. Почему вы не можете писать 0x01, 0x02 и т. Д., Хотя ... в конце концов, это фактические символы ASCII. – jrockway

+1

Он хочет, чтобы содержимое памяти (фактические байты) было 0x010203 (66051 десятичное), а не строка «0x010203». – KeithB

+1

Я изменил вопрос. Надеюсь, теперь это становится яснее. – FireAphis

ответ

5

Ну, просто используйте символы, а не целые числа.

s << char(1) << char(2) << char(3); 
30

Для чтения и записи двоичных данных в потоках, в том числе stringstreams, использовать для чтения() и записи) функции-члены (. Таким образом,

unsigned char a(1), b(2), c(3), d(4); 
std::stringstream s; 
s.write(reinterpret_cast<const char*>(&a), sizeof(unsigned char)); 
s.write(reinterpret_cast<const char*>(&b), sizeof(unsigned char)); 
s.write(reinterpret_cast<const char*>(&c), sizeof(unsigned char)); 
s.write(reinterpret_cast<const char*>(&d), sizeof(unsigned char)); 

s.read(reinterpret_cast<char*>(&v), sizeof(unsigned int)); 
std::cout << std::hex << v << "\n"; 

Это дает 0x4030201 в моей системе.

Edit: Чтобы сделать эту работу прозрачно для вставки и извлечения операторов (< < и >>), лучше всего это создать производный streambuf, что делает правильно, и передать, что любые потоки вы хотите использовать.

+0

Он определенно отвечает на первую часть вопроса, но есть ли способ сделать так, чтобы вставка выглядела одинаково (т. Е. S << a), но внутреннее представление данных различается в зависимости от типа потока? – FireAphis

+0

Ваш собственный streambuf не может этого сделать; форматирование выполняется в (не виртуальных) методах istream и ostream, и результат этого - то, что видит streambuf. – 2010-07-17 00:30:54

+0

Вопрос на самом деле показывает результат в памяти '0x010203', тогда как это, скорее всего, вызовет' 0x00000001 0x00000002 0x00000003' (при условии 'sizeof (int) == 4'). – MSalters

1

Перегрузка некоторых необычных операторов работает довольно хорошо. Здесь ниже я выбрал перегружать < =, потому что он имеет ту же левую к правой ассоциативности как < < и имеет как-то близкий вид и ощущение ...

#include <iostream> 
#include <stdint.h> 
#include <arpa/inet.h> 

using namespace std; 

ostream & operator<= (ostream& cout, string const& s) { 
    return cout.write (s.c_str(), s.size()); 
} 
ostream & operator<= (ostream& cout, const char *s) { 
    return cout << s; 
} 
ostream & operator<= (ostream&, int16_t const& i) { 
    return cout.write ((const char *)&i, 2); 
} 
ostream & operator<= (ostream&, int32_t const& i) { 
    return cout.write ((const char *)&i, 4); 
} 
ostream & operator<= (ostream&, uint16_t const& i) { 
    return cout.write ((const char *)&i, 2); 
} 
ostream & operator<= (ostream&, uint32_t const& i) { 
    return cout.write ((const char *)&i, 4); 
} 

int main() { 
    string s("some binary data follow : "); 

    cout <= s <= " (machine ordered) : " <= (uint32_t)0x31323334 <= "\n" 
     <= s <= " (network ordered) : " <= htonl(0x31323334) ; 
    cout << endl; 

    return 0; 
} 

Есть несколько недостатков :

  • новый смысл < = может ввести в заблуждение читателей или привести к неожиданным результатам:

    cout <= 31 <= 32; 
    

    не даст тот же результат, как

    cout <= (31 <= 32); 
    
  • порядок байтов четко не упоминалось при чтении кода, как показано в приведенном выше примере.

  • не может смешать просто с < <, потому что она не принадлежит к той же группе старшинства.Я обычно использую круглые скобки, чтобы уточнить такие как:

    (cout <= htonl(a) <= htonl(b)) << endl; 
    
+2

Это классное доказательство концепции, но обратите внимание, что перегруженные операторы C++ считаются злыми, потому что они позволяют * this *. Неочевидная перегрузка '<<' оправдана только потому, что это ** стандартная ** перегрузка. Никаких новых хакерских перегрузок не следует изобретать, и перегрузка должна использоваться с большой осторожностью. – cubuspl42

0

Для этого случая использования я реализовал себя «сырой оператор сдвига»:

template <typename T, class... StreamArgs> 
inline std::basic_ostream<StreamArgs...> & 
operator <= (std::basic_ostream<StreamArgs...> & out, T const & data) { 
     out.write(reinterpret_cast<char const *>(&data), sizeof(T)); 
     return out; 
} 

Поместите его где-то удобно и использовать его как это :

std::cout <= 1337 <= 1337ULL <= 1337. <= 1337.f; 

Преимущества:

  • цепного
  • автоматического sizeof()
  • принимает массивы и экземпляры структуры/класса тоже

Недостатков:

  • небезопасных для не-POD объектов: утечки указателей и дополнения
  • вывод специфичен для платформы: заполнение, энтианс, целые типы
0

Вы можете делать подобные вещи с помощью шаблонов. Например:

//struct to hold the value: 
template<typename T> struct bits_t { T t; }; //no constructor necessary 
//functions to infer type, construct bits_t with a member initialization list 
//use a reference to avoid copying. The non-const version lets us extract too 
template<typename T> bits_t<T&> bits(T &t) { return bits_t<T&>{t}; } 
template<typename T> bits_t<const T&> bits(const T& t) { return bits_t<const T&>{t}; } 
//insertion operator to call ::write() on whatever type of stream 
template<typename S, typename T> 
S& operator<<(S &s, bits_t<T> b) { 
    return s.write((char*)&b.t, sizeof(T)); 
} 
//extraction operator to call ::read(), require a non-const reference here 
template<typename S, typename T> 
S& operator>>(S& s, bits_t<T&> b) { 
    return s.read((char*)&b.t, sizeof(T)); 
} 

Он может использовать некоторую очистку, но это функционально. Например:

//writing 
std::ofstream f = /*open a file*/; 
int a = 5, b = -1, c = 123456; 
f << bits(a) << bits(b) << bits(c); 

//reading 
std::ifstream f2 = /*open a file*/; 
int a, b, c; 
f >> bits(a) >> bits(b) >> bits(c);