Two headers that provide abstractions to manipulate Bounded Strings and Stream of Blocks in C++ with a cleaner syntax.
by Esp. Ing. José María Sola
Professor
UTN FRBA
The standard type std::string
provide strings with dynamic and unbounded length, but it comes with a trade off, it's tedious to put them into or get them from fixed size objects.
The type String<N>
provides a solution, based upon a template type that wraps a std::array<char,N>
and a pair of functions to pack and unpack from and to bouned and unbounded strings: PackString
and UnpackString
Just copy the header StringN.h
to your C++ project folder, include it in your source files, and start packing and unpacking strings:
#include "StringN.h"
#include <cassert>
int main(){
std::string s{"Hello, World!"}; // Standard unbounded string s.
String<5> a = PackString(s); // Put s into bounded string a.
auto t{UnpackString(a)}; // Get the string from a.
assert("Hello" == t); // Check.
}
Using the standard functions read
and write
can be cumbersome and requieres the use of low level concepts. These are example statements that use those two funcitons:
out.write(reinterpret_cast<char*>(&block),sizeof block);
in.read(reinterpret_cast<const char*>(&block),sizeof block);
Each time we need to write or read, we end up copy-pasting that un-intuitive boilerplate code.
We requiere:
- Send three arguments (the stream, the start of the character block, its size).
- Do one
reinterpret_cast
. - Use of a pointer type.
- Use of the address-of operator (
&
). - And in the case of
read
, use ofconst
.
The header BlockStream.h
abstracts out all six actions, with a cleaner an simpler syntax:
WriteBlock(out, block);
ReadBlock(in, block);
Just copy the header BlockStream.h
to your C++ project folder, include it in your source files, and start writing and reading blocks to and from file streams.
#include "BlockStream.h"
#include <cassert>
int main(){
using Byte = unsigned char; // std::byte C++17 #include<cstddef>
struct Color{Byte red, green, blue;}; // A sample block.
constexpr auto filename{"color.bin"};
std::ofstream out{filename, std::ios::binary}; // Connect to write.
WriteBlock(out, Color{70,130,180}); // Write to out.
out.close(); // Disconnect.
std::ifstream in{filename, std::ios::binary}; // Connect to read.
Color steelBlue; // Object to store read data.
ReadBlock(in, steelBlue); // Read from in.
in.close(); // Disconcect.
remove(filename); // C++17 // std::filesystem::remove(filename);
assert( 70 == steelBlue.red );
assert( 130 == steelBlue.green );
assert( 180 == steelBlue.blue );
}
When combined, these headers allow easy manipulation of streams of fixed size records with bounded strings in them:
Son dos headers que proveen abstracciones para manipular Cadenas Acotadas y Flujo de Bloques en C++ con una sintaxis clara.
por Esp. Ing. José María Sola
Profesor
UTN FRBA
El tipo standar std::string
provee cadenas con largo dinámico y no acotado, pero viene con una contra, es tedioso almacerlas o leerlas en o desde objetos de tamaño fijo.
El tipo String<N>
provee una solución, basada en un tipo template que envuelve un std::array<char,N>
y en un par de funciones para empaquetar y desempaquetar desde y hacia cadenas acotadas y no acotadas: PackString
y UnpackString
Con simplemente copiar el encabezado StringN.h
a tu carpeta de proyecto C++, e incluirlo en tus archivos fuente, ya es posible empaquetar y desempaquetas cadenas.
#include "StringN.h"
#include <cassert>
int main(){
std::string s{"Hello, World!"}; // Standard unbounded string s.
String<5> a = PackString(s); // Put s into bounded string a.
auto t{UnpackString(a)}; // Get the string from a.
assert("Hello" == t); // Check.
}
Usar las funciones estdándar read
y write
puede ser incómodo y requiere el uso de conceptos de bajo nivel. Estos son ejemplos de sentencias que usan esas dos funciones:
out.write(reinterpret_cast<char*>(&block),sizeof block);
in.read(reinterpret_cast<const char*>(&block),sizeof block);
Cada vez que necesitamos usar write
o read
, terminamos copiando-y-pegando ese código repetitivo y poco intuitivo.
El uso requiere:
- Enviar tres argumentos (el flujo, el comienzo del bloque de caracteres, su tamaño).
- Hacer un
reinterpret_cast
. - Usar un tipo puntero.
- Usar el operador dirección-de (
&
). - Y en el caso de
read
, usarconst
.
El encabezado BlockStream.h
abstrae las seís acciones, con una sintaxis más clara y simple:
WriteBlock(out, block);
ReadBlock(in, block);
Con simplemente copiar el encabezado BlockStream.h
a su carpeta de proyecto C++, e incluirlo en sus archivos fuente, ya es posible escribir y leer bloques a y desde flujos.
#include "BlockStream.h"
#include <cassert>
int main(){
using Byte = unsigned char; // std::byte C++17 #include<cstddef>
struct Color{Byte red, green, blue;}; // A sample block.
constexpr auto filename{"color.bin"};
std::ofstream out{filename, std::ios::binary}; // Connect to write.
WriteBlock(out, Color{70,130,180}); // Write to out.
out.close(); // Disconnect.
std::ifstream in{filename, std::ios::binary}; // Connect to read.
Color steelBlue; // Object to store read data.
ReadBlock(in, steelBlue); // Read from in.
in.close(); // Disconcect.
remove(filename); // C++17 // std::filesystem::remove(filename);
assert( 70 == steelBlue.red );
assert( 130 == steelBlue.green );
assert( 180 == steelBlue.blue );
}
Cuando se combinan, estos encabezados permiten una manipulación fácil de flujos de registros de tamaño fijo con cadenas acotadas.