Во встроенном приложении, над которым я работаю, мне часто приходится пакетировать данные из нескольких различных объектов для отправки через последовательный порт. Аналогично, данные поступают на последовательный порт, который необходимо записать в несколько различных объектов.
Из соображений совместимости упорядочение данных в пакетах не позволяет последовательно размещать все данные, связанные с одним объектом, в пакете. Поэтому я не могу легко векторизовать и т. Д. Данные из каждого объекта, а затем поместить все это в пакет.
Я могу добиться надлежащей функциональности, используя очень большое количество получателей / мутаторов, которые класс пакетов использует для создания / декодирования переданных / полученных пакетов. Но это не выглядит очень элегантно.
Я мог бы сделать класс пакета другом класса, из которого он извлекает / записывает данные, но мне всегда говорили избегать использования друзей, так как это нарушает объектно-ориентированные принципы.
В идеале классы, которые обрабатывают реальное приложение, ничего бы не знали о классе пакета (и не должны были бы предоставлять геттеры / мутаторы исключительно для него), и у них просто были бы новые данные, если пакет попал между обновлениями.
Я подумал, что, возможно, я мог бы передать ссылки или указатели на соответствующие переменные-члены классу пакета при запуске, но это также сложно, потому что все члены не имеют одинаковый размер. Возможно, я мог бы также передать информацию о размере? Будет ли способ векторизовать список пустых указателей на член и размер пар членов, чтобы класс пакета не нуждался в гигантском количестве аргументов своего конструктора?
Я не уверен, насколько хорошо я описал эту проблему, но я могу предоставить разъяснения и дополнительную информацию, если это поможет. Большое спасибо за любые идеи.
Сокращенный пример класса пакета tx в настоящее время:
class PacketTx {
private:
uint8_t buffer[MAX_PACKET_SIZE]; // MAX_PACKET_SIZE is 200
public:
PacketTx(object1 *firstObject,
object2 *secondObject,
object3 *thirdObject
// ...etc...
)
void sendPacket(void);
};
void PacketTx::sendPacket(void) {
uint32_t i = 0;
int16_t tempVar1 = firstObject->getVar1();
uint32_t tempVar2 = secondObject->getVar2();
uint8_t tempVar3 = firstObject->getVar3();
int32_t tempVar4 = thirdObject->getVar4();
// ...etc...
memcpy(buffer + i, &tempVar1, sizeof(tempVar1));
i += sizeof(tempVar1);
memcpy(buffer + i, &tempVar2, sizeof(tempVar2));
i += sizeof(tempVar2);
memcpy(buffer + i, &tempVar3, sizeof(tempVar3));
i += sizeof(tempVar3);
memcpy(buffer + i), &tempVar4, sizeof(tempVar4));
i += sizeof(tempVar4);
// ...etc...
for(uint32_t j = 0; j < i; ++j)
putc(static_cast<char>(buffer[i]));
}
Этот пример не включает заголовок, контрольную сумму и т. Д., Но он должен дать общее представление о том, что вызывает у меня головную боль.
Так что, если я понимаю, у вас есть некоторый класс C, который имеет произвольное количество членов, которые должны быть записаны в буфер в произвольном порядке. Тогда тот же механизм происходит в обратном порядке.
Serial -> Packet.buffer () -> Object (и наоборот)
Моя первоначальная мысль использовала boost::tuple
и наличие типов соответствует порядку данных в пакете. (Но, возможно, более 50 членов / записей в кортеже, что может сильно ударить во время компиляции)
например
tuple<float, int, std::string> a(1.0f, 2, std::string("Some Words");
ostr << a;
Это также потребовало бы от ваших классов принимать кортеж в качестве аргумента и заполнять записи из данных. Поэтому вместо записи в буфер можно перечислить поля, хранящиеся в кортеже, чтобы можно было написать «хороший» код:
enum Fields {Price,Amount,Reason};
typedef boost::tuple<...> MyTuple;
void MyClass::getData( MyTuple& t )
{
t<Price>() = mPrice;
t<Amount>() = mAmount;
t<Reason>() = mSomeReason;
}
Примечание будет работать в обратном порядке.
Не уверен, что это полностью соответствует тому, что вы пытались сделать, но может вызвать новые мысли?
Композиция — это то, что вам нужно здесь:
class packet {
enum PacketType {
type1,
...
};
std::vector<unsigned char> buffer;
PacketType id;
public:
void setData(std::vector<unsigned char> buffer);
void setType(PacketType type);
std::vector<usigned char> getData();
PacketType getType();
//serialize Packet;
};
class composition_packet {
//other packet related detail.
std::vector<packet> data;
public:
void addPacket(packet p);
void send();
//serialize composition_packet
};
Идея состоит в том, чтобы сделать несколько пакетов по мере необходимости, но отправить их как один большой пакет (composition_packet
). Другой концепцией здесь является то, что пакет отвечает только за сериализацию, а не за типы данных, о которых он должен знать. Данные в пакет должны быть уже сериализованы объектом, который знает, как его сериализовать.
struct foo {
std::string str;
uint_8 u8;
uint_32 u32;
packet getPacket() {
//simple dumb serialization
std::stringstream ss(str);
ss << "|"; //delimit character
ss << u8 << "|";
ss << u32;
std::string s(ss.str());
std::vector<unsigned char> data(s.begin(), s.end());
packet p;
p.setType(packet::fooType);
p.setData(data);
return p;
}
};