Question

In the course of trying to become a really good and professional programmer with no spaghetti code, I face some situations where the code keeps looking messy whatever I do, and I fail to use my functional programming skills and object oriented programming skills to write a nice and organized code without having repeated patterns, and I'm trying to solve such a problem today.

So I have a console/terminal program that measures many things from many devices, and the output is an ASCII table that goes to a text file. My concern is: how to pass this information in the most flexible and professional way.

So I write the header of my output as follows (three lines of header of name, unit and data-type):

    ofstream outputStream ("output.txt",std::ios::out);

    outputStream << "#:" <<
                   "timestamp________________" << "\t" <<
                   "ActionID" << "\t" <<
                   "targetR" << "\t" << "targetPhi" << "\t" << "targetZ" << "\t" << "pinstate" <<"\t" << "fluxgate_orient" <<"\t" <<
                   "fluxXMean" << "\t" << "fluxYMean" << "\t" << "fluxZMean" << "\t" <<
                   "fluxXStdDev" << "\t" << "fluxYStdDev" << "\t" << "fluxZStdDev" << "\t" <<
                   "fluxNumOfSamples" << "\t" <<
                   "Incl1RollMean" << "\t" << "Incl1TiltMean" << "\t" << "Incl2RollMean" << "\t" << "Incl2TiltMean" << "\t" <<
                   "Incl1RollStdDev" << "\t" << "Incl1TiltStdDev" << "\t" << "Incl2RollStdDev" << "\t" << "Incl2TiltStdDev" << "\t" <<
                   "InclNumOfSamples" << "\t" <<
                   "PotRMean" << "\t" << "PotPhiMean" << "\t" << "PotZMean" << "\t" <<
                   "PotFullMean" << "\t" <<
                   "PotRStdDev" << "\t" << "PotPhiStdDev" << "\t" << "PotZStdDev" << "\t" <<
                   "PotFullStdDev" << "\t" <<
                   "PotNumOfSamples" << "\t" <<
                   "IntegrationTime" << "\t" <<
                   "CsBeginFIDSetIndex" << "\t" <<
                   "LengthOfReceivedStr" << "\t" <<
                   "CsFullReceivedStr" << "\t" <<
                   "ID" << "\t" <<
                   "Comment" << std::endl;

outputStream << "#=" <<
                   "YYYY-MM-DD_HH:MM:SS.3_UTC" << "\t" <<
                   "unitless" << "\t" <<
                   "mm" << "\t" << "deg" << "\t" << "mm" << "\t" << "enum" <<"\t" << "unitless" <<"\t" <<
                   "nT" << "\t" << "nT" << "\t" << "nT" << "\t" <<
                   "nT" << "\t" << "nT" << "\t" << "nT" << "\t" <<
                   "unitless" << "\t" <<
                   "mrad" << "\t" << "mrad" << "\t" << "mrad" << "\t" << "mrad" << "\t" <<
                   "mrad" << "\t" << "mrad" << "\t" << "mrad" << "\t" << "mrad" << "\t" <<
                   "unitless" << "\t" <<
                   "mm" << "\t" << "deg" << "\t" << "mm" << "\t" <<
                   "V" << "\t" <<
                   "mm" << "\t" << "deg" << "\t" << "mm" << "\t" <<
                   "V" << "\t" <<
                   "unitless" << "\t" <<
                   "seconds" << "\t" <<
                   "unitless" << "\t" <<
                   "unitless" << "\t" <<
                   "unitless" << "\t" <<
                   "unitless" << "\t" <<
                   "unitless" << std::endl;

outputStream << "#%" <<
                   "string__________________" << "\t" <<
                   "ulong" << "\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" << "char" <<"\t" << "int" <<"\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" <<
                   "ulong" << "\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" << "double" << "\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" << "double" << "\t" <<
                   "ulong" << "\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" <<
                   "double" << "\t" <<
                   "double" << "\t" << "double" << "\t" << "double" << "\t" <<
                   "double" << "\t" <<
                   "ulong" << "\t" <<
                   "double" << "\t" <<
                   "ulonglong" << "\t" <<
                   "ulong" << "\t" <<
                   "string" << "\t" <<
                   "string" << "\t" <<
                   "string" << std::endl;

And then after that comes the data in a loop:

while(devices_are_recording)
{
        outputStream <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << GetTime_Qt() << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << coordinatesData[coordinatesIndex].actionID << "\t" <<
                           std::setprecision(positionOutputAccuracy.getValue()) << nemaMotors.getPosition_internal(R_CONTROLLER_AXIS_INDEX) << "\t" << nemaMotors.getPosition_internal(PHI_CONTROLLER_AXIS_INDEX) << "\t" << nanotecMotor.getPosition_internal() << "\t" << pin.getValue().getPinState() <<"\t" << GetFluxgateOrientiation(pin,allowedPinRVals,tolRPin.getValue()) << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << fluxgateMean.getValue().r << "\t" << fluxgateMean.getValue().phi << "\t" << fluxgateMean.getValue().z << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << fluxgateStdDev.getValue().r << "\t" << fluxgateStdDev.getValue().phi << "\t" << fluxgateStdDev.getValue().z << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << samplesRecorded_fluxgate/ (enableBuffering ? (fluxgateNumberOfChannels*2) : 1) << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << inclinometerMean.getValue()[0] << "\t" << inclinometerMean.getValue()[1] << "\t" << inclinometerMean.getValue()[2] << "\t" << inclinometerMean.getValue()[3] << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << inclinometerStdDev.getValue()[0] << "\t" << inclinometerStdDev.getValue()[1] << "\t" << inclinometerStdDev.getValue()[2] << "\t" << inclinometerStdDev.getValue()[3] << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << samplesRecorded_inclinometer << "\t" <<
                           std::setprecision(positionOutputAccuracy.getValue()) << potPosMean.getValue().r << "\t" << potPosMean.getValue().phi << "\t" << potPosMean.getValue().z << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << directSum_potentiometers[3]/samplesRecorded_potentiometers << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << potPosStdDev.getValue().r << "\t" << potPosStdDev.getValue().phi << "\t" << potPosStdDev.getValue().z << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << sqrt((directSquaresSum_potentiometers[3] -  directSum_potentiometers[3]*directSum_potentiometers[3]/samplesRecorded_potentiometers ) / (samplesRecorded_potentiometers-1))  << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << samplesRecorded_potentiometers << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << coordinatesData[coordinatesIndex].measurementDuration << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << CsCurrentIndex << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << CsLengthOfStrReceived << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << CsFullReceivedStr << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << coordinatesData[coordinatesIndex].coordinatesIdentifier << "\t" <<
                           std::setprecision(defaultOutputAccuracy.getValue()) << coordinatesData[coordinatesIndex].comment << "\t" <<
                           endl;

}

You see, there are many many columns and they all have to be aligned... a change in the columns could very easily introduce an error in the output, because I'm counting the columns myself.

What's the best way to write such a thing in C++?

Was it helpful?

Solution

You can use polymorphism here: Define a base class

struct quantity {
    virtual std::string name();
    virtual std::string unit();
    virtual std::string datatype();

    virtual std::string measurement();
}

and define derived classes with suitable implementations for each quantity.

Then, you can have a std::vector<std::unique_ptr<quantity>> q; and the printing of the headers and measurement values becomes a simple for loop over q.

OTHER TIPS

For the first problem, a suggestion is to encapsulate the labels´print in a function:

1) Store your column names at a constant std::array or std::vector.

#include<vector>

const std::vector<std::string> col_labels = {"col_label1, col_label2, col_label3"};

So you may get something like:

void printHeaders(std::ofstream& outputStream){
    const std::vector<std::string> col_labels = {"col_label1", 
                                                "col_label2", 
                                                "col_label3"};
    for(auto& label: col_labels){
        outputStream << label << "\t";
    }
}

2) For the second, if you have different print patterns, for instance, you can use std::function to pass function as parameters to other functions and have more control over the patterns. You can declare the functions will be used as parameters or use a lambda expression.

NOTE: You're going to need C++11 support in your compiler. In g++ use the std=c++11 option.

For instance, suppose you have two different patterns: a) Enclose the field value in * b) Enclose the field value in ( ) c) Enclose the field value in -- --

#include<fstream>
#include<functional>
#include<string>

std::string formatAsterix(const std::string& str){
    //bad design concat std::strings... just to show the idea
    return std::string("*") + str + std::string("*");  
} 

std::string formatParenthesis(const std::string& str){
    return std::string("(") + str + std::string(")"); 
} 

void printFormatted(std::ofstream& outputStream, /*Your output stream*/
                    const std::string str, /*your string*/
                    std::function<std::string(const std::string&)> formatter ) /*your formatter function*/
{
    outputStream << formatter(str) << std::endl;
}


int main(){
    std::ofstream outputStream; 
    outputStream.open("c:\\tmp\\out.txt");

    printFormatted(outputStream, "FIELD_VALUE", formatAsterix);
    printFormatted(outputStream, "FIELD_VALUE", formatParenthesis);

    // This last one uses a lamba expression
    printFormatted(outputStream, "FIELD_VALUE", 
    [&](const std::string str)
    { 
       return std::string("-- ") + str + std::string(" --");    
    });


    outputStream.close();
}

Executing the program, you´ll get:

*FIELD_VALUE*
(FIELD_VALUE)
-- FIELD_VALUE --

I hope these examples could show you a test of functional programming in C++.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top