Thrift Required Field C++ - c++

Thrift File:
struct Sub {
1: required i32 number
}
struct Message {
1: required Sub sub
}
Message m;
m.write(protocol.get());
Message parsedM;
parsedM.read(protocol2.get());
std::cout << parsedM.sub.number;
SourceCode
Should not Thrift raise an error or is there any other possibiliy to properly check if all required field have been set?
Otherwise I see no real point in that keyword.

Short answer
Yes, you should get a TProtocolException::INVALID_DATA if the data are not present while deserializing. That's not the case in your example, so you don't get the exception.
Long answer
This is one of the generated C++ read() methods based on your IDL. Watch out for isset_number and how it is used:
uint32_t Sub::read(::apache::thrift::protocol::TProtocol* iprot) {
uint32_t xfer = 0;
std::string fname;
::apache::thrift::protocol::TType ftype;
int16_t fid;
xfer += iprot->readStructBegin(fname);
using ::apache::thrift::protocol::TProtocolException;
bool isset_number = false;
while (true)
{
xfer += iprot->readFieldBegin(fname, ftype, fid);
if (ftype == ::apache::thrift::protocol::T_STOP) {
break;
}
switch (fid)
{
case 1:
if (ftype == ::apache::thrift::protocol::T_I32) {
xfer += iprot->readI32(this->number);
isset_number = true;
} else {
xfer += iprot->skip(ftype);
}
break;
default:
xfer += iprot->skip(ftype);
break;
}
xfer += iprot->readFieldEnd();
}
xfer += iprot->readStructEnd();
if (!isset_number)
throw TProtocolException(TProtocolException::INVALID_DATA);
return xfer;
}
So why does it work in your example?
As we have seen, required fields are expected to be present in the deserialized data, while optional (and default) fields may be missing. But fields marked required (and default fields) are also always written to the transport, optional only when a value has been assigned. Hence you read the data that you serialized before:
uint32_t Sub::write(::apache::thrift::protocol::TProtocol* oprot) const {
uint32_t xfer = 0;
xfer += oprot->writeStructBegin("Sub");
xfer += oprot->writeFieldBegin("number", ::apache::thrift::protocol::T_I32, 1);
xfer += oprot->writeI32(this->number);
xfer += oprot->writeFieldEnd();
xfer += oprot->writeFieldStop();
xfer += oprot->writeStructEnd();
return xfer;
}
Note that Thrift does not care about whether the field contains valid data. It's all about the serializing aspect.
Recommended reading
Diwaker Gupta's "Missing Guide" explains the pros ands cons of required quite good.

Related

using a bytes field as proxy for arbitrary messages

Hello nano developers,
I'd like to realize the following proto:
message container {
enum MessageType {
TYPE_UNKNOWN = 0;
evt_resultStatus = 1;
}
required MessageType mt = 1;
optional bytes cmd_evt_transfer = 2;
}
message evt_resultStatus {
required int32 operationMode = 1;
}
...
The dots denote, there are more messages with (multiple) primitive containing datatypes to come. The enum will grow likewise, just wanted to keep it short.
The container gets generated as:
typedef struct _container {
container_MessageType mt;
pb_callback_t cmd_evt_transfer;
} container;
evt_resultStatus is:
typedef struct _evt_resultStatus {
int32_t operationMode;
} evt_resultStatus;
The field cmd_evt_transfer should act as a proxy of subsequent messages like evt_resultStatus holding primitive datatypes.
evt_resultStatus shall be encoded into bytes and be placed into the cmd_evt_transfer field.
Then the container shall get encoded and the encoding result will be used for subsequent transfers.
The background why to do so, is to shorten the proto definition and avoid the oneof thing. Unfortunately syntax version 3 is not fully supported, so we can not make use of any fields.
The first question is: will this approach be possible?
What I've got so far is the encoding including the callback which seems to behave fine. But on the other side, decoding somehow skips the callback. I've read issues here, that this happened also when using oneof and bytes fields.
Can someone please clarify on how to proceed with this?
Sample code so far I got:
bool encode_msg_test(pb_byte_t* buffer, int32_t sval, size_t* sz, char* err) {
evt_resultStatus rs = evt_resultStatus_init_zero;
rs.operationMode = sval;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
/*encode container*/
container msg = container_init_zero;
msg.mt = container_MessageType_evt_resultStatus;
msg.cmd_evt_transfer.arg = &rs;
msg.cmd_evt_transfer.funcs.encode = encode_cb;
if(! pb_encode(&stream, container_fields, &msg)) {
const char* local_err = PB_GET_ERROR(&stream);
sprintf(err, "pb_encode error: %s", local_err);
return false;
}
*sz = stream.bytes_written;
return true;
}
bool encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
evt_resultStatus* rs = (evt_resultStatus*)(*arg);
//with the below in place a stream full error rises
// if (! pb_encode_tag_for_field(stream, field)) {
// return false;
// }
if(! pb_encode(stream, evt_resultStatus_fields, rs)) {
return false;
}
return true;
}
//buffer holds previously encoded data
bool decode_msg_test(pb_byte_t* buffer, int32_t* sval, size_t msg_len, char* err) {
container msg = container_init_zero;
evt_resultStatus res = evt_resultStatus_init_zero;
msg.cmd_evt_transfer.arg = &res;
msg.cmd_evt_transfer.funcs.decode = decode_cb;
pb_istream_t stream = pb_istream_from_buffer(buffer, msg_len);
if(! pb_decode(&stream, container_fields, &msg)) {
const char* local_err = PB_GET_ERROR(&stream);
sprintf(err, "pb_encode error: %s", local_err);
return false;
}
*sval = res.operationMode;
return true;
}
bool decode_cb(pb_istream_t *istream, const pb_field_t *field, void **arg) {
evt_resultStatus * rs = (evt_resultStatus*)(*arg);
if(! pb_decode(istream, evt_resultStatus_fields, rs)) {
return false;
}
return true;
}
I feel, I don't have a proper understanding of the encoding / decoding process.
Is it correct to assume:
the first call of pb_encode (in encode_msg_test) takes care of the mt field
the second call of pb_encode (in encode_cb) handles the cmd_evt_transfer field
If I do:
bool encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
evt_resultStatus* rs = (evt_resultStatus*)(*arg);
if (! pb_encode_tag_for_field(stream, field)) {
return false;
}
if(! pb_encode(stream, evt_resultStatus_fields, rs)) {
return false;
}
return true;
}
then I get a stream full error on the call of pb_encode.
Why is that?
Yes, the approach is reasonable. Nanopb callbacks do not care what the actual data read or written by the callback is.
As for why your decode callback is not working, you'll need to post the code you are using for decoding.
(As an aside, Any type does work in nanopb and is covered by this test case. But the type_url included in all Any messages makes them have a quite large overhead.)

CppUnitTestFramework: Test Method Fails, Stack Trace Lists Line Number at the End of Method, Debug Test Passes

I know, I know - that question title is very much all over the place. However, I am not sure what could be an issue here that is causing what I am witnessing.
I have the following method in class Project that is being unit tested:
bool Project::DetermineID(std::string configFile, std::string& ID)
{
std::ifstream config;
config.open(configFile);
if (!config.is_open()) {
WARNING << "Failed to open the configuration file for processing ID at: " << configFile;
return false;
}
std::string line = "";
ID = "";
bool isConfigurationSection = false;
bool isConfiguration = false;
std::string tempID = "";
while (std::getline(config, line))
{
std::transform(line.begin(), line.end(), line.begin(), ::toupper); // transform the line to all capital letters
boost::trim(line);
if ((line.find("IDENTIFICATIONS") != std::string::npos) && (!isConfigurationSection)) {
// remove the "IDENTIFICATIONS" part from the current line we're working with
std::size_t idStartPos = line.find("IDENTIFICATIONS");
line = line.substr(idStartPos + strlen("IDENTIFICATIONS"), line.length() - idStartPos - strlen("IDENTIFICATIONS"));
boost::trim(line);
isConfigurationSection = true;
}
if ((line.find('{') != std::string::npos) && isConfigurationSection) {
std::size_t bracketPos = line.find('{');
// we are working within the ids configuration section
// determine if this is the first character of the line, or if there is an ID that precedes the {
if (bracketPos == 0) {
// is the first char
// remove the bracket and keep processing
line = line.substr(1, line.length() - 1);
boost::trim(line);
}
else {
// the text before { is a temp ID
tempID = line.substr(0, bracketPos - 1);
isConfiguration = true;
line = line.substr(bracketPos, line.length() - bracketPos);
boost::trim(line);
}
}
if ((line.find("PORT") != std::string::npos) && isConfiguration) {
std::size_t indexOfEqualSign = line.find('=');
if (indexOfEqualSign == std::string::npos) {
WARNING << "Unable to determine the port # assigned to " << tempID;
}
else {
std::string portString = "";
portString = line.substr(indexOfEqualSign + 1, line.length() - indexOfEqualSign - 1);
boost::trim(portString);
// confirm that the obtained port string is not an empty value
if (portString.empty()) {
WARNING << "Failed to obtain the \"Port\" value that is set to " << tempID;
}
else {
// attempt to convert the string to int
int workingPortNum = 0;
try {
workingPortNum = std::stoi(portString);
}
catch (...) {
WARNING << "Failed to convert the obtained \"Port\" value that is set to " << tempID;
}
if (workingPortNum != 0) {
// check if this port # is the same port # we are publishing data on
if (workingPortNum == this->port) {
ID = tempID;
break;
}
}
}
}
}
}
config.close();
if (ID.empty())
return false;
else
return true;
}
The goal of this method is to parse any text file for the ID portion, based on matching the port # that the application is publishing data to.
Format of the file is like this:
Idenntifications {
ID {
port = 1001
}
}
In a separate Visual Studio project that unit tests various methods, including this Project::DetermineID method.
#define STRINGIFY(x) #x
#define EXPAND(x) STRINGIFY(x)
TEST_CLASS(ProjectUnitTests) {
Project* parser;
std::string projectDirectory;
TEST_METHOD_INITIALIZE(ProjectUnitTestInitialization) {
projectDirectory = EXPAND(UNITTESTPRJ);
projectDirectory.erase(0, 1);
projectDirectory.erase(projectDirectory.size() - 2);
parser = Project::getClass(); // singleton method getter/initializer
}
// Other test methods are present and pass/fail accordingly
TEST_METHOD(DetermineID) {
std::string ID = "";
bool x = parser ->DetermineAdapterID(projectDirectory + "normal.cfg", ID);
Assert::IsTrue(x);
}
};
Now, when I run the tests, DetermineID fails and the stack trace states:
DetermineID
Source: Project Tests.cpp line 86
Duration: 2 sec
Message:
Assert failed
Stack Trace:
ProjectUnitTests::DetermineID() line 91
Now, in my test .cpp file, TEST_METHOD(DetermineID) { is present on line 86. But that method's } is located on line 91, as the stack trace indicates.
And, when debugging, the unit test passes, because the return of x in the TEST_METHOD is true.
Only when running the test individually or running all tests does that test method fail.
Some notes that may be relevant:
This is a single-threaded application with no tasks scheduled (no race condition to worry about supposedly)
There is another method in the Project class that also processes a file with an std::ifstream same as this method does
That method has its own test method that has been written and passes without any problems
The test method also access the "normal.cfg" file
Yes, this->port has an assigned value
Thus, my questions are:
Why does the stack trace reference the closing bracket for the test method instead of the single Assert within the method that is supposedly failing?
How to get the unit test to pass when it is ran? (Since it currently only plasses during debugging where I can confirm that x is true).
If the issue is a race condition where perhaps the other test method is accessing the "normal.cfg" file, why does the test method fail even when the method is individually ran?
Any support/assistance here is very much appreciated. Thank you!

Convert NDEF record payload to string for EthernetClient print()

I try to make a HTTP GET request with data read from an NDEF formatted MIFARE NFC tag. I fail to convert the byte array data from the tag into a format that works with the Ethernet client print() function.
Hardware setup is an Arduino Uno with a seeedstudio NFC Shield and an Arduino ethernet shield. I make use of the ethernet, the PN532, and the NfcAdapter library.
I tried several types of conversion using char * and char[] instead of the String object with no success.
To pinpoint the problem I picked a case where Serial.print() gives the expected result but client.print() does not.
Code is based on the PN532 NDEF library example 'ReadTagExtended'.
void loop(void)
{
if (nfc.tagPresent()) // Do an NFC scan to see if an NFC tag is present
{
NfcTag tag = nfc.read(); // read the NFC tag
if (tag.hasNdefMessage())
{
NdefMessage message = tag.getNdefMessage();
for (int i = 0; i < message.getRecordCount(); i++)
{
NdefRecord record = message.getRecord(i);
int payloadLength = record.getPayloadLength();
byte payload[payloadLength];
record.getPayload(payload);
String tag_content = "";
for(int i = 0; i<payloadLength; i++) {
tag_content += (char)payload[i];
}
Serial.println(tag_content); // prints the correct string
request(tag_content);
}
}
}
}
void request(String data) {
EthernetClient client;
// if you get a connection, report back via serial:
if (client.connect(remote, 8080)) {
client.print("GET /subaddress");
client.print("?data=");
client.print(data); // unfortunately empty
client.println();
client.println();
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.print(c);
}
}
client.stop();
Serial.println(" OK");
delay(100);
} else {
Serial.println("ERR");
delay(100);
}
}
With the above setup, I get the expected output using Serial.println(). Yet, in the (successfull) request, data is empty.
From the comments (summarized):
Printing the record type (record.getType()) gives the letter 'U'. payloadLength is 4 for a tag containing the string "def".
Based on the information that you provided in your comment, the tag contains a URI record (that, in turn, contains your data). The problem in your code is that you directly use the full payload of the URI record as a string. However, a URI record contains more that just a string. Particularly, the first byte of the payload is the prefix byte (typically a non-printable character). So it seems that Serial.println() (or rather your serial receiver) simply skips that character. client.print() will include that character into the HTTP request and will consequently create an invalid HTTP request (that looks as if the remaining bytes were omitted).
Therefore, you will have to follow the URI Record Type Definition to decode the payload into a proper URI before using it:
NdefMessage message = tag.getNdefMessage();
for (int i = 0; i < message.getRecordCount(); ++i) {
NdefRecord record = message.getRecord(i);
if (record.getType() == "U") {
String uri = "";
int payloadLength = record.getPayloadLength();
if (payloadLength > 0) {
byte payload[payloadLength];
record.getPayload(payload);
switch (payload[0]) {
case 0x000: break;
case 0x001: uri += "http://www."; break;
case 0x002: uri += "https://www."; break;
case 0x003: uri += "http://"; break;
case 0x004: uri += "https://"; break;
case 0x005: uri += "tel:"; break;
case 0x006: uri += "mailto:"; break;
case 0x007: uri += "ftp://anonymous:anonymous#"; break;
case 0x008: uri += "ftp://ftp."; break;
case 0x009: uri += "ftps://"; break;
case 0x00A: uri += "sftp://"; break;
case 0x00B: uri += "smb://"; break;
case 0x00C: uri += "nfs://"; break;
case 0x00D: uri += "ftp://"; break;
case 0x00E: uri += "dav://"; break;
case 0x00F: uri += "news:"; break;
case 0x010: uri += "telnet://"; break;
case 0x011: uri += "imap:"; break;
case 0x012: uri += "rtsp://"; break;
case 0x013: uri += "urn:"; break;
case 0x014: uri += "pop:"; break;
case 0x015: uri += "sip:"; break;
case 0x016: uri += "sips:"; break;
case 0x017: uri += "tftp:"; break;
case 0x018: uri += "btspp://"; break;
case 0x019: uri += "btl2cap://"; break;
case 0x01A: uri += "btgoep://"; break;
case 0x01B: uri += "tcpobex://"; break;
case 0x01C: uri += "irdaobex://"; break;
case 0x01D: uri += "file://"; break;
case 0x01E: uri += "urn:epc:id:"; break;
case 0x01F: uri += "urn:epc:tag:"; break;
case 0x020: uri += "urn:epc:pat:"; break;
case 0x021: uri += "urn:epc:raw:"; break;
case 0x022: uri += "urn:epc:"; break;
case 0x023: uri += "urn:nfc:"; break;
default: break;
}
for (int j = 1; j < payloadLength; ++j) {
uri += (char)payload[j]; // NOTE: this is wrong since the string is UTF-8 encoded (but we translate it byte-by-byte)
}
}
Serial.println(uri);
request(uri);
}
}
Be aware that there a still a few issues with the solution above:
The string part of the URI (bytes starting at offset 1) is actually UTF-8 encoded. However, we treat it as if it was (more or less) ASCII encoded here. I haven't found any simple library to properly transform byte arrays into a UTF-8 encoded string representation on Arduino though.
If your resulting URI contains characters that have special meaning in URLs (such as "/", ":", "&", etc.), you would need to urlencode (see e.g. here) them before appending the string to the GET request. Otherwise, the resulting URI of the GET request may not be what you expected.
String is certainly not the best choice in terms of memory efficiency.

Refactoring switch or if/else statement? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 5 years ago.
Improve this question
i'm working on a school project and got some feedback from my teacher. He said that in my code there are some bad practices, he said that the switch cases could be replaced by a polymorphic approach. Only i have no clue how i could do this.
My code is receiving messages from a CAN bus. Those messages come from different devices, I check the messages from which device they come from. If there is a new device I create a object and parse the message and store the information.
This system is pretty much the same for each message.
Here is my code.
void Application::PollWhisperConnectBus()
{
HAL_GPIO_TogglePin(PORT_LED1, PIN_LED1);
whisper_connect_id_ = hcan2.pRxMsg->StdId;
if (whisper_connect_id_ >= 0x580 && whisper_connect_id_ <= 0x58F)
{
WIBDevice();
}
if (whisper_connect_id_ >= 0x590 && whisper_connect_id_ <= 0x59F)
{
BMSSDevice();
}
if (whisper_connect_id_ >= 0x5B0 && whisper_connect_id_ <= 0x5BF)
{
DCPowerCubeDevice();
}
if (whisper_connect_id_ >= 0x5C0 && whisper_connect_id_ <= 0x5CF)
{
ACPowerCubeDevice();
}
if (whisper_connect_id_ >= 0x700 && whisper_connect_id_ <= 0x70F)
{
WIBHeartBeatDevice();
}
}
This is one of the functions which checked if there is an object of the class, if so parse the message.
void Application::DCPowerCubeDevice()
{
bool found_device = false;
int device = (hcan2.pRxMsg->StdId & 0x0F) + device_instance_offset_;
WhisperConnectDevice* whisper_connect_device;
for(unsigned int i = 0; i < whisper_connect_device_list_.size(); ++i)
{
if ((whisper_connect_device = whisper_connect_device_list_.at(i)) != NULL &&
whisper_connect_device->GetClassName() == "DCPowerCube")
{
DCPowerCube* dc_powercube = dynamic_cast<DCPowerCube*>(whisper_connect_device);
if (dc_powercube != NULL)
{
if (dc_powercube->GetDevice() == device)
{
dc_powercube->ParseCanMessage(&hcan2);
found_device = true;
break;
}
}
}
}
if (!found_device)
{
WhisperConnectDevice* dc_powercube;
if ((dc_powercube = new DCPowerCube) != NULL)
{
dc_powercube->SetDevice(device);
int n2k_address = nmea2000_.FindFirstFreeCanId(n2k_address_, device_list_);
if (n2k_address != 0xFFFF)
{
dc_powercube->SetSrcCanId(n2k_address);
dc_powercube->SetDeviceInstanceOffset(device_instance_offset_);
dc_powercube->SetDeviceInstance(0x30 + device);
dc_powercube->AddressClaim(nmea2000_);
dc_powercube->SendPGN126996(nmea2000_);
dc_powercube->SendPGN126998(nmea2000_, "DCPowerCube", "", "");
device_list_.at(n2k_address) = 0x01;
}
DCPowerCube* dc_powercube2 = dynamic_cast<DCPowerCube*>(dc_powercube);
if (dc_powercube2 != NULL)
{
dc_powercube2->SetCurrentLimit(16);
}
AddToWPCDeviceList(dc_powercube);
}
}
}
void DCPowerCube::ParseCanMessage(CAN_HandleTypeDef *can_handle)
{
if (can_handle != NULL)
{
uint16_t message_index = (can_handle->pRxMsg->Data[1] << 8) + can_handle->pRxMsg->Data[2];
switch (message_index)
{
case 0x1008:
device_name_[0] = can_handle->pRxMsg->Data[4];
device_name_[1] = can_handle->pRxMsg->Data[5];
device_name_[2] = can_handle->pRxMsg->Data[6];
device_name_[3] = can_handle->pRxMsg->Data[7];
device_name_[4] = '\0';
break;
case 0x100A:
software_version_[0] = can_handle->pRxMsg->Data[4];
software_version_[1] = can_handle->pRxMsg->Data[5];
software_version_[2] = can_handle->pRxMsg->Data[6];
software_version_[3] = can_handle->pRxMsg->Data[7];
software_version_[4] = '\0';
break;
case 0x1018:
serial_number_ = can_handle->pRxMsg->Data[4] << 24 | can_handle->pRxMsg->Data[5] << 16 |
can_handle->pRxMsg->Data[6] << 8 | can_handle->pRxMsg->Data[7];
break;
case 0x2100: // DC PowerCube status
power_cube_status_ = can_handle->pRxMsg->Data[4];
io_status_bit_ = can_handle->pRxMsg->Data[5];
dip_switch_status_bit_ = can_handle->pRxMsg->Data[6];
break;
case 0x2111: // Grid voltage, current, current limit
grid_voltage_ = (can_handle->pRxMsg->Data[4] << 8) + can_handle->pRxMsg->Data[5];
grid_current_ = can_handle->pRxMsg->Data[6];
grid_current_limit_ = can_handle->pRxMsg->Data[7];
break;
case 0x2112: // Generator frequency, RPM
generator_freq_ = (can_handle->pRxMsg->Data[4] << 8) + can_handle->pRxMsg->Data[5];
rpm_ = (can_handle->pRxMsg->Data[6] << 8) + can_handle->pRxMsg->Data[7];
break;
case 0x2113: // Generator current
gen_current_phase1_ = can_handle->pRxMsg->Data[4];
gen_current_phase2_ = can_handle->pRxMsg->Data[5];
gen_current_phase3_ = can_handle->pRxMsg->Data[6];
gen_current_limit_ = can_handle->pRxMsg->Data[7];
break;
case 0x2114: // Load percentage
grid_load_ = can_handle->pRxMsg->Data[4];
generator_load_ = can_handle->pRxMsg->Data[5];
dc_output_load_ = can_handle->pRxMsg->Data[6];
break;
case 0x2151: // Battery type & charger state
battery_type_ = can_handle->pRxMsg->Data[4];
charger_state_ = can_handle->pRxMsg->Data[5];
break;
case 0x2152: // DC output voltage & DC slave voltage
dc_output_voltage_ = (can_handle->pRxMsg->Data[4] << 8) + can_handle->pRxMsg->Data[5];
dc_slave_voltage_ = (can_handle->pRxMsg->Data[6] << 8) + can_handle->pRxMsg->Data[7];
break;
case 0x2153: // DC output current & DC output current limit
dc_output_current_ = (can_handle->pRxMsg->Data[4] << 8) + can_handle->pRxMsg->Data[5];
dc_output_current_limit_ = (can_handle->pRxMsg->Data[6] << 8) + can_handle->pRxMsg->Data[7];
break;
case 0x21A0: // Temperature sensor
temp_sens_BTS_ = can_handle->pRxMsg->Data[4];
temp_sens_intern1_ = can_handle->pRxMsg->Data[5];
temp_sens_intern2_ = can_handle->pRxMsg->Data[6];
temp_sens_intern3_ = can_handle->pRxMsg->Data[7];
break;
case 0x21A1:
break;
}
}
}
The WhisperConnectDevice is the base class of DCPowerCube.
I would love to get some feedback on how to approach this problem.
Whether or not you introduce polymorphism it appears you have to map an externally provided type number (ID) to code so you will always need some structure inbetween.
Your candidates are:
A block of if statements probably if-else-if...
A switch statement (if values are ameanable)
Some kind of look-up table (array, associative map, other...)
You've already got if but could improve with if-else-if.
That is normally considered the ugliest high-maintenance potential coding hot-spot approach. Coding hot-spot because all new IDs return to this code block.
I also notice in this case all your ranges are 0xnn0 to 0xnnF inclusive for some nn so you can at least simplify by reducing out the low 4 bits:
auto whisper_connect_type = whisper_connect_id_ >> 4;
Your switch option is then simplified to:
switch(whisper_connect_type) {
case 0x58: WIBDevice(); break;
case 0x59: BMSSDevice(); break;
case 0x5B: DCPowerCubeDevice(); break;
case 0x5C: ACPowerCubeDevice(); break;
case 0x70: WIBHeartBeatDevice(); break;
default: HandleUnknownDeviceIDError(whisper_connect_id_); break;
}
NB: I very strongly recommend some code to handle an unsupported ID. My advice is throwing an exception or something leading to termination. The break; is for completeness. I don't think you're coming back from an unknown ID.
An alternative is to define an associative map:
#include <iostream>
#include <unordered_map>
#include <memory>
class WhisperHandler {
public:
virtual void HandleWhisper() const = 0 ;
virtual ~WhisperHandler() {}
};
class WhisperHandlerWIBDevice : public WhisperHandler {
public:
void HandleWhisper() const override {
std::cout << "Handler WIBDevice...\n";
}
} ;
int main() {
std::unordered_map<unsigned,std::unique_ptr<const WhisperHandler>> handlers;
//...
std::unique_ptr<const WhisperHandler> handler(std::make_unique<const WhisperHandlerWIBDevice>());
std::pair<const unsigned , std::unique_ptr<const WhisperHandler> > pair({0x5B,std::move(handler)});
handlers.insert(std::move(pair));
//...
{
const auto &chandlers=handlers;
auto handlerit(chandlers.find(0x5B1));
if(handlerit!=chandlers.end()){
handlerit->second->HandleWhisper();
}else{
//ERROR - UNKNOWN HANDLER.
}
}
return 0;
}
I would suggest however you're only going to get return on investment for all this polymorphic machinery if you're going to allow the registration of handlers dynamically either from different modules of the application or by dynamically loading libraries that register themselves on load.
If it's a single project application (which it appears to be) then the switch table dispatch will probably work fine.
Because applications tend to communicate using IDs of some kind OO can start to look cumbersome when it in practice it needs to take an ID, map it to a polymorphic handler and then call the handler. Logically you've done the ID to logic mapping twice!
Footnote: The trick of knocking out the lowest 4-bits is somewhat separate from these methods and (of course) slightly fragile if the lower 4 bits become relevant to determining the handler down the line.

How to avoid Switch Case & sscanf Function

I am working on C++. I am writing code for date format. Using with, we can get default date format from anyother date format. So I have found 240 date formats for this task. So M want to use switch case and sscanf function. Every case have sscanf function to separate day,month, year. So I need 240 cases and 240 sscanf function. Is there any method to avoid lot of swtich and sscanf? If you have any ideas, please let me know guys.
case 0:
sscanf(tsdate.c_str(),"%2d/%2d/%4d",&day,&month,&year);
break;
case 1:
sscanf(tsdate.c_str(),"%2d-%2d-%4d",&month,&day,&year);
break;
case 2:
sscanf(tsdate.c_str(),"%2d %2d %4d",&day,&month,&year);
break;
case 3:
sscanf(tsdate.c_str(),"%2d/%2d/%2d",&day,&month,&year);
coryear(year);
break;
case 4:
sscanf(tsdate.c_str(),"%2d/%2d/%2d",&year,&month,&day);
coryear(year);
break;
Like above, I want to put 240 cases and 240 sscanf. Please let me know how to avoid a lot of cases.
you can't avoid the switch cases but you can create multiple function to avoid sscanf:
void scanDayFirst(string format)
{
sscanf(tsdate.c_str(),format,&day,&month,&year);
break;
}
void scanMonthFirst(string format)
{
sscanf(tsdate.c_str(),format,&month,&day,&year);
break;
}
and so on.....
the result will be like this:
case 0:
scanDayFirst("%2d/%2d/%4d");
case 1:
scanDayFirst("%2d-%2d-%4d");
case 2:
scanDayFirst("%2d %2d %4d");
case 3:
scanDayFirst("%2d.%2d.%4d");
enum { ITEM_YEAR, ITEM_MONTH, ITEM_DAY, NUM_ITEMS };
struct date_format { char const *fmt; int items[NUM_ITEMS]; };
struct date_format const formats[] =
{ { "%2d/%2d/%4d", { ITEM_DAY, ITEM_MONTH, ITEM_YEAR } }
, { "%2d-%2d-%4d", { ITEM_MONTH, ITEM_DAY, ITEM_YEAR } }
/* etc. */
};
int parts[NUM_ITEMS]; /* Instead of year,month,day */
sscanf(tsdate.c_str(), formats[x].fmt,
&parts[formats[x].items[0]],
&parts[formats[x].items[1]],
&parts[formats[x].items[2]]);
You can extend this to have ITEM_NONE if you want to skip an item, or add extra items, and so on.
NB. If this is C++ then consider using stream input instead of sscanf.
Instead of enumerating all cases consider instead using a custom language for the format specification:
Date x = parseDate(user_input, "dd-mm-yyyy");
it will make the function shorter, easier to document and easier to use also improving the readability of the code that uses it. The idea is using codes like
yyyy ............. 4-digits year
yy ............... 2-digits year with automatic century computation
mm ............... 2-digits month
m ................ 1 or 2 digits month
dd ............... 2-digits day
d ................ 1 or 2 digits day
anything else .... mandatory character
A simple implementation could be
Date parseDate(const std::string& input, const std::string& format) {
const char *src = input.c_str();
const char *fmt = format.c_str();
int year=-1, month=-1, day=-1;
while (*fmt) {
if (!*src) throw invalid_date(input, format);
if (strncmp(fmt, "yyyy", 4) == 0) {
fmt += 4;
year = getInt(src, 4);
} else if (strncmp(fmt, "yy", 2) == 0) {
fmt += 2;
year = guess_century(getInt(src, 2));
} else if (strncmp(fmt, "mm", 2) == 0) {
fmt += 2;
month = getInt(src, 2);
} else if (fmt[0] == 'm') {
fmt += 1;
month = getInt(src, -1);
} else if (strncmp(fmt, "dd", 2) == 0) {
fmt += 2;
day = getInt(src, 2);
} else if (fmt[0] == 'd') {
fmt += 1;
day = getInt(src, -1);
} else {
if (src[0] != fmt[0]) throw invalid_date(input, format);
src++; fmt++;
}
}
if (*src || year == -1 || month == -1 || day == -1)
throw invalid_date(input, format);
return Date(year, month, day);
}
NOTE: untested