Elegant object-oriented member access without a lot of getters / mutators

In the embedded application I'm working on, I often have to package data from several different objects that will be sent through the serial port. Similarly, data arrives at the serial port, which must be written to several different objects.

For compatibility reasons, organizing data in packages does not allow you to sequentially put all the data associated with a single object in a package. Therefore, I cannot easily vectorize, etc. Data from each object, and then put it all in a package.

I can achieve the proper functionality using the very large number of getters / mutators that the package class uses to create / decode transmitted / received packets. But it does not seem very elegant.

I could make the package class a friend of the classes that it retrieves / writes data from / to, but I was always told to avoid using friends, as this type violates object-oriented principles.

Ideally, the classes that process the actual application do not know anything about the package class (and should not only provide getters / mutators exclusively for it), and they will simply have new data if the package gets between updates.

I thought that maybe I could pass references or pointers to the corresponding member variables to the package class at startup, but this is also difficult because all members do not have the same size. Perhaps I can also convey size information? Would it be possible to vectorize the list of void pointers to a member and the size of pairs of elements so that the package class does not need a huge number of arguments to its constructor?

I am not sure how well I described this problem, but I can of course provide clarifications and additional information if that helps. Thanks so much for any ideas.

A shortened example of the tx package class currently:

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])); } 

This example does not include a heading, checksum, etc., but it should give a general idea of ​​what causes my headache.

+4
source share
2 answers

So, if I understand that you have some class C that has an arbitrary number of members that must be written to the buffer in random order. Then the same mechanism happens in the opposite way.

Serial -> Packet.buffer () -> Object (and reverse)

My initial thought was to use boost::tuple , and the types corresponded to the order of the data in the package. (But maybe 50+ members / records in the tuple, which can be a long compilation time)

eg

 tuple<float, int, std::string> a(1.0f, 2, std::string("Some Words"); ostr << a; 

It will also require your classes to take a tuple as an argument and populate the records from the data. Therefore, instead of writing to the buffer, there may be an enumeration of the fields stored in the tuple so that you can write "good" code:

 enum Fields {Price,Amount,Reason}; typedef boost::tuple<...> MyTuple; void MyClass::getData( MyTuple& t ) { t<Price>() = mPrice; t<Amount>() = mAmount; t<Reason>() = mSomeReason; } 

Note. Will work in reverse order.

Not sure if this is completely consistent with what you were trying to do, but might trigger some new thoughts?

+2
source

Composition is what you need here:

 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 }; 

The idea here is to make several packages as needed, but send them as one big package ( composition_packet ). Another concept here is that the package is only responsible for serializing itself, and not for the data types that it should be aware of. The data in the package should already be serialized by an object that knows how to serialize it.


 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; } }; 
+1
source

Source: https://habr.com/ru/post/1442312/


All Articles