Шестнадцатеричная печать в STL поток

Когда-то очень давно я написал элементарный манипулятор для шестнадцатеричной печати в стандарный поток. Все просто и тривиально. Но тем не менее я заметил, что таскаю этот микрокласс почти в кажный проект, где нужна отладочная печать. Обычно для шестнадцатеричной печати надо указывать сразу несколько итераторов, типа:

std::cout << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << 0xAA;

Причем std::setw() надо повторять для каждого нового выводимого элемента. Я свел все это в один итератор, чтобы можно было просто написать (указав итератору ширину выводимого поля):

std::cout << ext::Hex(2) << 0xAA;

Итак, класс Hex (название пространства имен можно подкрутить по вкусу), файл hex.h:

#ifndef _EXT_HEX_H
#define _EXT_HEX_H

#include <iostream>
#include <iomanip>

namespace ext {

class Hex {
public:
  Hex(int width) : __width(width) {}
  friend std::ostream& operator<< (std::ostream& os, const Hex& hex);
private:
  int __width;
};

inline std::ostream& operator<< (std::ostream& os, const Hex& hex) {
  std::hex(os);
  std::uppercase(os);
  os.width(hex.__width);
  os.fill('0');
  return os;
}

} // ext

#endif // _EXT_HEX_H

Теперь можно писать так:

std::cout << ext::Hex(0)  << 0x0a << std::endl;
std::cout << ext::Hex(1)  << 0x0a << std::endl;
std::cout << ext::Hex(1)  << 0xaa << std::endl;
std::cout << ext::Hex(2)  << 0xaa << std::endl;
std::cout << ext::Hex(4)  << 0xaa << std::endl;
std::cout << ext::Hex(8)  << 0x0a << std::endl;
std::cout << ext::Hex(16) << 0x0a << std::endl;
std::cout << ext::Hex(32) << 0x0a << std::endl;

И результатом будет:

A
A
AA
AA
00AA
0000000A
000000000000000A
0000000000000000000000000000000A

На всякий случай, unit-тест. Чтобы не было сюрпризов при обновлении компилятора, STLport или чего-то еще. Тест всегда проверит, работает ли класс так, как вы от него ждете. Вы можете возразить — ну класс-то выеденного яйца не стоит, а тут для него тесты… Соглашусь. А еще я соглашусь, что сотни раз самые казалось бы ненужные на первый взгляд тесты для “очевидных” классов помогали обнаружить глюки на новой версии системных библиотек, новой версии компилятора, использовании “более мощных” параметров оптимизации и т.д. Время на написание тестов всегда окупается сполна, всегда.

Традиционно, для компиляции тестов нам нужна Google Test Framework. Как я уже писал, вы можете скачать мою модификацию этой библиотеки, которая сокращена до двух необходимых файлов gtest/gtest.h и gtest-all.cc.

Файл hex_unittest.cpp:

#include "gtest/gtest.h"
#include "hex.h"
#include <sstream>

void testHex(int n, int w, const std::string& etalon) {
  std::stringstream fmt;
  fmt << ext::Hex(w) << n;
  EXPECT_EQ(etalon, fmt.str());
}

TEST(HexManip, Generic) {
  testHex(0x0A, 0, "A");
  testHex(0x0A, 1, "A");
  testHex(0xAA, 1, "AA");
  testHex(0xAA, 2, "AA");
  testHex(0xAA, 4, "00AA");
  testHex(0xAA, 8, "000000AA");
  testHex(0xAA, 16, "00000000000000AA");
  testHex(0xAA, 32, "000000000000000000000000000000AA");
}
Ну и головная программа:
#include "gtest/gtest.h"
int main(int argc, char* argv[]) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

Компилируем.

Visual Studio:

cl /EHsc /I. /Fehex_unittest_vs2008.exe runner.cpp hex_unittest.cpp gtest\gtest-all.cc

Cygwin:

g++ -I. -o hex_unittest_cygwin.exe runner.cpp hex_unittest.cpp gtest/gtest-all.cc

Запускаем:

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from HexManip
[ RUN      ] HexManip.Generic
[       OK ] HexManip.Generic
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran.
[  PASSED  ] 1 test.

Работает как положено.

При использовании Hex у себя в проекте не забудьте включить файл hex_unittest.cpp в ваш набор unit-тестов. Оберегите себя от ненужной траты времени в будущем.

Под занавес пара слов о производительности. Очевидно, что если вы выводите в поток десятки тысяч шестнадцатеричных чисел подряд, то разумнее будет использовать стандартные итераторы — настроить поток с помощью std::hex, std::uppercase и std::setfill, а потом вызывать только std::setw для каждого нового элемента. Но если вы печатаете разнородные данные, что часто требуется при отладке, то тогда итератор Hex будет в самый раз.

Другие посты по теме:


Оригинальный пост | Disclaimer

Комментарии