Рассмотрим следующий класс c ++ 11, который представляет структуру заголовка IPv4, которая должна создаваться из байтового массива независимо от порядка байтов.
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Net {
using addr_t = ::in_addr_t;
#pragma pack(push, 1)
struct ip_header_t {
uint8_t ver_ihl;
uint8_t tos;
uint16_t total_length;
uint16_t id;
uint16_t flags_fo;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
addr_t src_addr;
addr_t dst_addr;
ip_header_t( const uint8_t* bytes, const bool ntoh = false ) {
auto o = (ip_header_t&)*bytes;
ver_ihl = o.ver_ihl;
tos = o.tos;
ttl = o.ttl;
protocol = o.protocol;
total_length = ntoh? ntohs(o.total_length) : o.total_length;
id = ntoh? ntohs(o.id) : o.id;
flags_fo = ntoh? ntohs(o.flags_fo) : o.flags_fo;
checksum = ntoh? ntohs(o.checksum) : o.checksum;
src_addr = ntoh? ntohl(o.src_addr) : o.src_addr;
dst_addr = ntoh? ntohl(o.dst_addr) : o.dst_addr;
};
};
#pragma pack(pop)
}
Я обеспокоен тем, что принятие байтового массива может быть не самым безопасным или наиболее семантически правильный способ сделать это. Приведение массива в качестве самой структуры кажется очень методом C-ish, в котором отсутствует безопасность типов (не говоря уже о проверке границ). Было бы лучше потребовать от вызывающего абонента беспокоиться об этом и потребовать константную ссылку на экземпляр?
Преобразование байтового массива в этот класс определенно не является правильным решением, поскольку, как вы упоминали, порядок следования байтов в разных системах может быть разным (вот почему ntohs
в конструкторе).
То, куда вы положите свой класс, полностью зависит от ролей и обязанностей ваших лиц. Не видя дизайн, невозможно сказать.
Тип для представления необработанных двоичных данных с предполагаемой компоновкой некоторого вида:
template<typename T, size_t order>
struct serial_tag {};
Представляем некоторые имена, представляющие ожидаемый тип и расположение данных на диске:
typedef serial_tag<uint8_t , 0> ver_ihl_ser;
typedef serial_tag<uint8_t , 1> tos_ser;
typedef serial_tag<uint16_t, 2> total_length_ser;
...
typedef serial_tag<addr_t , 9> dst_addr_ser;
Пакет serial_tags, которым затем можно манипулировать с помощью другого кода:
template<typename... tags>
struct serial_pack {};
Напишите код, который принимает serial_pack и гарантирует, что каждый порядковый номер используется без пробелов.
Напишите код, который принимает итератор десериализации и serial_tag и продвигает этот итератор десериализации при генерации данных из serial_tag. Это должно обращаться с порядком байтов
Цель состоит в том, чтобы описать макет необработанных данных таким образом, чтобы его можно было метапрограммировать, а затем загрузить данные в структуру C ++ с использованием этой информации макета.
Это операция потокового чтения, где итератор десериализации (или диапазон) знает, имеет ли он ограничение на его размер, и знает, правильно ли вы читаете элементы по порядку (по крайней мере, в отладке).
Я не знаю, стоит ли это того, но это решает ваши проблемы.
Недостатком этого подхода является то, что он нарушает DRY, поскольку теоретически макет в памяти можно использовать для описания постсериализации необработанного байта. Вместо этого мы должны поддерживать совершенно другой набор данных, чтобы представить это. Как плюс, это означает, что наш макет в C ++ не должен точно копировать двоичный макет.
Мне кажется, что лучшим решением было бы предоставить конструктор копирования, который мог бы обрабатывать перевод в байтовом порядке и полагаться на вызывающую сторону для выполнения приведения.
Вот так:
/* copy constructor: */
ip_header_t( const ip_header_t& src, const bool ntoh = false )
: ver_ihl(src.ver_ihl),
tos(src.tos),
ttl(src.ttl),
protocol(src.protocol) {
total_length = ntoh? ntohs(src.total_length) : src.total_length;
id = ntoh? ntohs(src.id) : src.id;
flags_fo = ntoh? ntohs(src.flags_fo) : src.flags_fo;
checksum = ntoh? ntohs(src.checksum) : src.checksum;
src_addr = ntoh? ntohl(src.src_addr) : src.src_addr;
dst_addr = ntoh? ntohl(src.dst_addr) : src.dst_addr;
};
/* client code using byte array in network-order */
auto ip_header = Net::ip_header_t((Net::ip_header_t&)*(byte_array), true);
Я не уверен на 100%, что мне больше нравится это решение. Возможно, еще лучше создать функцию, не являющуюся членом, которая делает это, учитывая, что перестановка порядка байтов не имеет прямого отношения к конструкции объекта. Также, возможно, это не законная обязанность класса заботиться о выравнивании и упорядочении полей.