C++은 stream을 통해 반복자(iterator)를 이용해 데이터를 매우 유연하게 처리 할 수 있습니다. 이 스트림을 통해 데이터의 입력을 키보드나 파일 등을 통해 매우 유연하고 효과적으로 읽고 쓸 수 있는데요. 문제는 일반화된 문자열에 대한 읽고 쓰기에 중심을 두고 있다는 점입니다. 즉, C++의 스트림을 이용해 실수 타입의 값으로 3.14159265라는 값을 파일에 저장하고자 한다면, 이 값을 메모리에 저장된 그대로의 실수형 바이너리 형식으로의 저장이 아닌 “3.14159265”라는 문자열로 저장되는 것이 C++ 개발자에게 흔히 노출된 방식이라는 것입니다.
다 이유가 있겠으나, 필자는 이 실수형 값을 메모리에 저장된 그대로의 실수형 바이너리 형식으로 파일에 저장하고, 이렇게 저장된 값을 다시 파일을 열어 꺼내와 화면에 표시하고자 합니다.
먼저 3.14159265 값을 d 드라이브의 data.bin에 저장하는 코드입니다.
#include <iostream> #include <fstream> int main() { std::ofstream os("d:\\data.bin", std::ios::binary); double v = 3.141592625; os.write(reinterpret_cast<const char*>(&v), sizeof(double)); os.close(); }
실행해 보면 data.bin 파일이 생성되어졌고, 파일 크기는 double의 바이트 크기와 동일한 8바이트인 것을 확인할 수 있습니다.
이제 이렇게 생성된 파일에서 다시 실수형 값을 읽어 보는 코드는 다음과 같습니다.
#include <iostream> #include <fstream> int main() { std::ifstream is("d:\\data.bin", std::ios::binary); double v2; is.read(reinterpret_cast<char*>(&v2), sizeof(double)); is.close(); std::cout << v2; }
여기서 C++의 template을 이용해 개선을 해보도록 하겠습니다. 즉, 다음과 같은 2개의 템플릿 함수를 추가합니다.
template<typename T> std::ostream& write_typed_data(std::ostream& stream, const T& value) { return stream.write(reinterpret_cast<const char*>(&value), sizeof(T)); } template<typename T> std::istream & read_typed_data(std::istream& stream, T& value) { return stream.read(reinterpret_cast<char*>(&value), sizeof(T)); }
네, write_typed_data와 read_typed_data는 각각 임이의 데이터 타입에 대해서 지정된 스트림에 메모리에 저장된 그대로의 구조인 바이너리 형식으로 쓰고 읽는 범용 함수입니다.
이 두 함수를 활용해 3.141592625 값을 쓰고 읽는 전체 코드를 다시 작성해 보면 다음과 같습니다.
#include <iostream> #include <fstream> template<typename T> std::ostream& write_typed_data(std::ostream& stream, const T& value) { return stream.write(reinterpret_cast<const char*>(&value), sizeof(T)); } template<typename T> std::istream & read_typed_data(std::istream& stream, T& value) { return stream.read(reinterpret_cast<char*>(&value), sizeof(T)); } int main() { // Write std::ofstream os("d:\\data.bin", std::ios::binary); double v = 3.141592625; //os.write(reinterpret_cast<const char*>(&v), sizeof(double)); write_typed_data(os, v); os.close(); // <- test unit end // Read std::ifstream is("d:\\data.bin", std::ios::binary); double v2; read_typed_data(is, v2); //is.read(reinterpret_cast<char*>(&v2), sizeof(double)); is.close(); std::cout << v2; // <- test unit end }
C++에서 스트림을 활용해 타입을 가지는 변수를 바이너리 형식으로 저장하는 위의 방식이 정석인지는 모르겠습니다. (이제와서 이게 무슨 소리? -_-;) reinterpret_cast를 사용했다는 점에서 상당히 의구심이 들기 때문인데요. 더욱 나은 방식이 있다면 제안해 주시기 바랍니다. reinterpret_cast는 포인터가 다른 포인터 형식으로 변환될 수 있도록 하거나 정수 계열 형식이 포인터 형식으로 변환될 수 있도록 하고 그 반대로도 변환될 수 있도록 하는 cast 연산자입니다.