Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 23 of 23

Thread: Class function pointer, point to different class functions for different instances

  1. #1
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17

    Class function pointer, point to different class functions for different instances

    I have made a class to describe a serial port. It contains (settings for) the port properties, baudrate, connection status, etc. AND a small number of handling functions.

    I need to make three instances of this class, one of these (Port1) will be connected to DCE (Data Communication Equipment) and two (Port2, Port3) will be connected to DTE (Data Terminal Equipment) . (colloquially known as PC's and Modems)

    In the Main Loop, i call the class' has_data() function on all three instances. Port1.has_data(), will read data available at Port1... etc
    This works, I can send and receive messages to and from all devices, but it is missing flexibility.
    I want the class to have 2 (or 3) different has_data() functions. (They may have 3 different names) and be able to assign one of them when creating the instance of the class.
    So that when i call Port1.has_data() it is handled by a different function in the class than when i call Port2.has_data().
    Ideally, the class should have a pointer to function, that i can point to any of the 3 member functions(assign in the constuctor)


    I have no idea what to google for. With some googeling i have found links to "pointers to member functions" , but it seem not exactly to provide what i need/want.
    Has anybody done something similar before?

  2. #2
    Senior Member
    Join Date
    Dec 2016
    Location
    Montreal, Canada
    Posts
    3,134
    It depends on your use case. Is it always identified as Port1,2,3?
    How would you tell them apart?
    How would you decide which object uses which functor?

  3. #3
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    Here a quick working example showing how that can be done. Pointers to member functions have a funny syntax I prefer type aliases over typedefs. E.g. "using hasDataPtr = void (Port::*)()".
    Please note: Usually you'd do the declaration in a file class.h and the definition in class.cpp. For the sake of simple testing I included all in the main sketch which compiles but is very bad style.

    Code:
    #include "Arduino.h"
    
    
    // The following usually goes into port.h -----------------------------------------
    
    class Port
    {
    public:
        Port(HardwareSerial *port);
    
        void hasData();
    
    protected:
        using hasDataPtr = void (Port::*)();  // define an alias for a member function pointer
    
        void hasData1();
        void hasData2();
    
        hasDataPtr hasData_x;  // pointer to the hasData functions
        HardwareSerial *port;
    };
    
    // this normally goes int port.cpp ---------------------------
    
    Port::Port(HardwareSerial *_port)
    {
        port = _port;
    
        // port->begin(xx) or whatever you want to do to setup the port
    
        // Now assign the correct hasData function, e.g. depending on the passed in HardwarePort, make sure that the pointer has a value in any case
        if (port == &Serial1)
            hasData_x = &Port::hasData1;
    
        else if (port == &Serial2)
            hasData_x = &Port::hasData2;
        // etc...
    }
    
    void Port::hasData()
    {
        (this->*hasData_x)();  // call the correct hasData function through the pointer
    }
    
    void Port::hasData1()
    {
        if(port->available() > 0)
        {
            //doSomething with the port
        }
        Serial.println("hasData1 was called");
    }
    
    void Port::hasData2()
    {
        if(port->available() > 0)
        {
            //doSomething with the port
        }
        Serial.println("hasData2 was called");
    }
    
    //=====================================================
    
    Port p1(&Serial1);
    Port p2(&Serial2);
    
    void setup()
    {
        while (!Serial);
    
        p1.hasData();
        p2.hasData();
    }
    
    void loop()
    {
    }
    However, this is a bit clumsy, using an abstract base class and virtual hasData_x functions in derived classes would be a more elegant way to achieve the same effect.
    Last edited by luni; 10-04-2019 at 06:04 PM.

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    BTW: This https://cdecl.org/ is an amazing page translating between plain English and complicated c declarations e.g. the following
    Code:
    declare arr as array of pointer to function (void) returning pointer to array 3 of int
    results in
    Code:
    int (*(*arr[])(void ))[3]
    Works in both directions...

    Unfortunately it knows c only and won't help you with the member function pointer.

  5. #5
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    OMG, so quickly and so complete.
    Thanks so much. I think this is exactly what I have been trying to do.

    It's late now (well, it is here), so I will look in more detail a bit later.
    I, indeed, find the syntax intimidating. :-)
    I do have the class neatly separated in PortT.h and PortT.cpp.

    Thanks again.

  6. #6
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Hi TonTon,

    I did read your post, and thank you.
    The use case is kinda like i described it?? I am as unsure how to specify my answer to you as you were to answer mine, sorry about lacking detail. LoL

    The Teensy's Serial resources (pins, through RS232 drivers) FOR THIS PROJECT are always going to be connected to the same external devices.
    I do have different projects that will use different devices and also different serial data handlers (that i refer to as has_data() in my original post).
    But in that case i will need to basically copy my project, write some new specific has_data() functions and recompile.

    How I expect to use it is something like this (but open to alternatives) :
    First, I declare and initialize all three of the Serial resources that i intend to use. (Serial_1, Serial_2 , Serial_3 )
    Then i create an instance of my Serial_Port class, passing it any one of the initialized Serial objects as a parameter
    Ser_Port My_Port1 = Ser_Port(Serial_1); // This will not change .. Let's say my computer will always be on My_port1, which will always use Teensy's Serial1.

    I do pass it some other parameters, such as a (pointer to a) list of commands that i expect on that port that should trigger some very specific actions, such as user alerts, and things that i can easily achieve by toggling pins. (One device may send a specific 'string' that starts with "error.." and i may want to switch on an LED, just for example)

    The reason i would like the _has_data() function to be called through a pointer to a function that resides inside the class, is that depending on some state of the 3 devices, the data needs to be handled differently. I would think it best to have the Serial_Port class have a buffer to receive the Serial data, that each of the has_data() functions could access, (one at a time, only when it is the current handler function). (Assigning the pointer to a different function will never happen when there is data already in the buffer.)
    The point here is that if one Serial port receives and detects a specific state of it's connected device, the sketch needs to switch the handling function on another port.

    Hope this gives you some idea of what i am trying to do.

    Thanks for reading.

  7. #7
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Hi Luni,

    I have written a response to TonTon just now, that explains in more detail what i expect the sketch to work like.
    I loved my own idea of having each instance of my class have it's pointer point to the specific has_data() function that instance should use.
    I am kinda intrigued also though by your suggestion to use a (abstract) base class (where you declare a function has_data) and then.. (if i understand you correctly)

    ... make a specific derived class for each of the specific types of device i think the sketch will connect on a specific port. ??

    If i understand correctly, that would invoke the sub-class' handler function when i call the _has_data on the level of the abstract class?

    But am i correct then that that would not allow me to change to an alternative handler function during the running of the sketch (as i did not clearly state in my original post, i think, and I am sorry. :-)

    And thanks again for your very complete code yesterday. It's above and beyond what i expected. And so useful because i struggled with the syntax of something like that for about a week.

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    How I expect to use it is something like this (but open to alternatives)
    It looks like you want to model a port class being compatible to various devices you want to attach. Usually I do such things the other way round. I.e, I try to model the class according to the device which I want to attach and not the port. To design the class, I ask myself questions like

    1. What does the device contain -> serial interface, Sensors etc,
    2. What actions does it support -> initalizing, shut down, readout sensors, etc


    Then, the answers to #1 define the modules / variables / objects the class should contain and #2 defines the member functions.

    In your case I'd do something like this (note: this is not compiling, it should just demonstrate the idea...)

    Code:
    class MyFirstDevice
    {
    public:
        MyFirstDevice(HardwareSerial *port);
        void begin(); // if needed
        void end();
        void tick();
    
        float getSensor1Value() { return sensor1Value; }
        float getSensor2Value() { return sensor2Value; }
    
    protected:
        HardwareSerial *port;
    
        float sensor1Value, sensor2Value;
    };
    
    
    // e.g. readout values in the background
    void MyFirstDevice::tick()
    {
        if(port->available())
        {
            sensor1Value = someValue;
            sensor2Value = someOtherValue;
        }
    }
    
    class MyPCDevice
    {
        public:
            MyPCDevice(HardwareSerial *port);
        /*
        ......
        */
    }
    
    MyFirstDevice machineA(&Serial1);
    MyFirstDevice machineB(&Serial2);
    MyPCDevice PC(&Serial3);
    
    void Setup()
    {
        // intialize stuff
    }
    
    elapsedMillis stopwatch = 0; 
    
    void loop()
    {
        machineA.tick();  // update devices in the background...
        machineB.tick();
    
        if(stopwatch > 500)
        {
            PC.SendMessage(machineA.getSensor1Value());
        }
    }
    Note that there is no need to do a dedicated logic for the ports, you only need dedicated logic for the devices. If you find that you often do similar devices you can consider writing a common base class and derive the actual classes from that base class.

    Hope that helps

  9. #9
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Yeah, i can see how it makes sense to model the machine (or model the way to control that machine) in a class, and let every Machine-class have function containing code to check the machine's serial port. (as you describe .. if port->available {do stuff} )

    And in cases where i was using a Teensy to literally read sensors and or user input and use these sensor values to control a machine, i think i would naturally come to that solution. It makes perfect sense.
    I just can't get my head away from the idea that all i want the Teensy to do is sit in the middle of two (or in my case three) devices, of which one is the controller, the other one a slave-device, and intercept their interaction and in specific cases change the way the controller's commands go through, while the Teensy is running.

    To be even more precise about the use case. It is a piece of equipment, which is out of warrenty and the manufacturer has gone out of business.
    (It's not exactly 2019 technology, that explains why it is 9600 baud Serial controlled)
    We had to replace manufacturer designed, but now obsolete parts of the machine for off the shelf parts. These new parts actually have better specifications (newer) but don't exactly use the same protocol.
    At a certain part of the process, the controller switches protocol. Rather than sending commands as text with a single integer parameter, it starts sending commands together, spaced with control characters.
    The controller sends a specific command to indicate it is going to send such packets.
    I would like to be using a different has_data() function that tokenizes that command string, check the individual commands, modify the parameters (if necessary) and rebuild a send buffer.


    So in your suggested design, i would need to make two classes for that one device (because it kinda acts as if it is a different machine on the same Serial port):
    One where has_data() only checks for the command to switch to the other protocol (and class) , and
    One class where has_data() tokenizes the command buffer and rebuilds the command in an output buffer.
    I would need a way to somehow let both classes share the same Serial resource on the Teensy.
    (or switch which of the two classes actually gets to use the Serial hardware when the specific command arrives)

    Another solution which is of course available is using a boolean parameter (receive_mode) and read every command into the class' buffer, then test and branch:

    If (receive_mode == original) { data_handler_1(&in_buffer); Serial_3 write(in_buffer) }

    else {data_handler_2(&in_buffer); Tokenize(in_buffer, out_buffer) ; Serial_3( write(out_buffer)); }


    Maybe i am making things too complicated, but i am trying to make the code re-usable, because i foresee a similar solution for some other cases.
    So ideally i was thinking i could keep basically the same Serial_Port class and just add a has_data_X function and change the pointer to it.
    (As a way to only having to maintain one sketch for different uses. That is also why i have a pointer to an array of char arrays containing the commands the Serial port needs to catch for specific actions)

    Anyway, As you can tell, i am not a software engineer.
    Your suggestions past and future, are very much appreciated, Thank you.

  10. #10
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    Ah, now comes the full information :-).

    • A controller sends data to one of the Teensy serial ports. The controller uses two protocols and can switch between them by a special command.
    • The Teensy parses the data received from the controller, translates the commands to yet another protocol and sends them to a slave connected at another serial port.
    • You want to write a framework making it easy to do similar command translating later on without reprogramming everything


    Did I understand that correctly? What about the 3rd device you mentioned?

  11. #11
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    It is probably too late in Taiwan to answer today, so I assumed my assumptions from above are correct and just for fun and because I had some time I generated two test classes for you.

    There is an abstract base class (Translator) which handles incoming data on the port and calls the currently selected parsers which need to be defined in a derived class:

    File "Translator.h"
    Code:
    #pragma once
    
    #include "Stream.h"
    
    /****************************************************
     * Abstract base class for the translator classes
     * call the tick function as often as possible
     * If a char is available the currently selected
     * parser function will be called with that character
    *****************************************************/
    
    enum class parserType {A,B};
    
    class Translator
    {
    public:
        inline Translator(Stream *inputStream, Stream *outputStream);
        inline void tick();
    
    protected:
        parserType currentParser;
    
        virtual void parserA(char c) = 0;
        virtual void parserB(char c) = 0;
    
        Stream *iStream, *oStream;
    };
    
    // IMPLEMENTATION ===============================================
    
    Translator::Translator(Stream *inputStream, Stream *outputStream)
    {
        iStream = inputStream;
        oStream = outputStream;
        currentParser = parserType::A;
    }
    
    void Translator::tick()
    {
        if (iStream->available())
        {
            char c = iStream->read(); // read one char from the stream
            switch (currentParser)
            {
            case parserType::A:
                parserA(c);
                break;
            case parserType::B:
                parserB(c);
                break;
            // can be extended if necessary...
            }
    
            iStream->print(c); //ECHO to see typed characters on the serial monitor, not required for the real code of course
        }
    }
    Here an example of a machine dependent derived class (M1Translator). You can use this as a template for further machine dependent implementations.
    The example has two parsers (A and B). One just forwards the incoming characters to the output port. When it receives the switch command (here a '*') it switches to parserB.
    ParserB replaces each incoming 'a' by '_a_' and incoming linefeeds ('\n') by ' | '. A '*' switches back to parserA.

    File "M1Translator.h"
    Code:
    #pragma once
    
    #include "Translator.h"
    
    /*---------------------------------------------------
      Example for a simple Translator class derived from
      the base Translator.You can define as many Translator
      classes as required.
    --------------------------------------------------*/
    
    class M1Translator : public Translator
    {
        public:
            inline M1Translator(Stream *inputStream, Stream *outputStream);
    
        protected:
            inline void parserA(char c);
            inline void parserB(char c);
    };
    
    
    // IMPLEMENTATION ===============================================
    
    
    M1Translator::M1Translator(Stream *iStream, Stream *oStream)
        : Translator(iStream, oStream)
    {
        currentParser = parserType::A; // start with parserA
    }
    
    // ParserA ---------------------------------------------------
    // Here parserA just forwards received characters to the output
    // If the parser detects a '*' it switches to parserB
    // Change to any required behaviour
    
    void M1Translator::parserA(char c)
    {
        if (c == '*')
        {
            currentParser = parserType::B;
        }
        else
        {
            oStream->print(c);
        }
    }
    
    // ParserB ---------------------------------------------------
    // parserB replaces "a" by '_a_' and removes '\n';
    // '*' switches back to parserA
    // The needs to be replaced of course by your parsing algorithm.
    
    void M1Translator::parserB(char c)
    {
        switch (c)
        {
        case 'a': // replace a by _a_
            oStream->print("_a_");
            break;
    
        case '\n': // replace \n by |
            oStream->print(" | ");
            break;
    
        case '*':  //switch back to parserA
            currentParser = parserType::A;
            break;
    
        default: // just forward character
            oStream->print(c);
            break;
        }
    }
    And here the fortunately clean main sketch
    Code:
    #include "M1Translator.h"
    
    M1Translator translator(&Serial, &Serial1); //input: Serial, output: Serial1
    
    
    void setup()
    {
        Serial.begin(0);
        Serial1.begin(115200);
    }
    
    void loop()
    {
        translator.tick();
    }
    The picture below shows the result of a quick test run. The left window is the controller talking on USB Serial and the right window is the slave listening on Serial1.
    You can see that ParserA just passes the characters to the Slave. After receiving a '*' it switches to ParserB which replaces 'a' and '\n' as described above

    Click image for larger version. 

Name:	translator.jpg 
Views:	9 
Size:	83.0 KB 
ID:	17822

    This is of course just one of a lot of possible solutions to your problem but it is simple and ease to reuse (just derive other XXX_translator classes from the base class and override the parser functions). At least it is something to give you a head start...
    Last edited by luni; 10-06-2019 at 05:41 PM.

  12. #12
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Luni,

    LoL, yeah.. you got that right. All of it. About the full information and about the time in Taiwan.
    Have you ever considered a career in fortune telling? In writing summaries? LoL.

    I am very grateful, even though you make me feel a little stupid :P
    (Let's say i am good at some other things)
    Monday is a working day that I am not spending on this particular issue.
    Please give me some time to read and comprehend the code you wrote.

    Really Really awesome. Thanks

  13. #13
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Hi Luni,

    Could of course not stop myself from having a look right away.
    Have some questions. I know the definition of 'inline' , but i have never (thought i) needed it. Is it simply your preference, or experience? Or is there a clear and obvious benefit.


    If the machine returns a status string, or some kind of 'acknowledge', would/could i create another instance of a Translator derived class ,with the Serial ports in reverse order and call tick on that?
    (Actually, the off the shelf parts do not supply the acknowledge, i could (in this case) simply include that where you write
    iStream->print(c); in the base class


    I still think it is a flexible approach. Let me try to implement this design idea of using base class and derived class and a switch case for the parser
    (That's a little similar to when i wrote if(receive_mode == original) etc etc.)
    I am not as fast as you writing code. So it is going to take some time to see if i can make it work in actual practice.

    Send me a message if you 're ever in Taipei, I will buy you dinner.
    Thanks for putting me on this track, its more than a head start.
    And i am also looking at the TyTools :-)

  14. #14
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    Have you ever considered a career in fortune telling?
    I'm the one guy in a quite large family whom everybody calls on their computer issues... So I'm somehow used to live with scattered and incomplete information :-)

    I know the definition of 'inline' , but i have never (thought i) needed it. Is it simply your preference, or experience? Or is there a clear and obvious benefit.
    'inline' is one of the most misunderstood keywords of c++. It does not at all suggest that the compiler should inline the code. Instead, it instructs the linker to accept the same code in more than one translation unit (cpp file). Since I placed the implementation of the class directly in the headers the member function code is potentially compiled into more than one object file and the linker would choke on that. Declaring the functions as inline tells the linker that this is by purpose and it can use the code from whatever object file it finds convenient. Here a very good stackoverflow article describing this much better than I can: https://stackoverflow.com/a/1759575/1842762

    Why did I place the code in the header? Just to make copying to the thread more easy and keep everything together. If you do the normal thing (seperate .h and .cpp) you do not need the inline keyword.


    If the machine returns a status string, or some kind of 'acknowledge', would/could i create another instance of a Translator derived class ,with the Serial ports in reverse order and call tick on that?
    Sure, but this again seems to be an overengineered idea. Can you explain what you actually want to achieve? Do you need to not only translate the commands from the controller but also the answers from the slave?

    (That's a little similar to when i wrote if(receive_mode == original) etc etc.)
    Yes, I originally tried your first idea with the function pointers but it makes the code quite ugly and the only thing it gains is to avoid one "if condition" which takes a few ns only and should be totally irrelevant.

    Send me a message if you 're ever in Taipei, I will buy you dinner.
    Not very likely but who knows ...

  15. #15
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Hi Luni,

    Well, i am not sure why that would be over engineered, isn't communication usually bi-directional?
    Kind of brings me back to an earlier question you raised ...
    Do we model a serial port in code
    Or
    Do we model a machine in code
    Its sometimes hard for me to forget i am not programming the controller (single serial port), but indeed a translator. The sketch really needs to control two Serial ports of the Teensy, one to handle (send and receive) the controller on one end, one to handle the slave on the other end.
    Writing this down already helps.. LoL


    In this specific application, the Controller tests if the slave is still connected and will not continue before it gets an acknowledge. (this is not hard to deal with)
    In another application (that i am not working on until this one is running stable) , the controller expects a status string of about 15 characters after every command it sends, and if no actual commands are sent, the controller will send a <CR> every 500ms just to get that status.
    At the very least i have to receive that response and let it pass through.
    AFAIK, the controller is OK to miss one response occasionally. It will send another <CR> and continue.


    If i am not bothering you yet, i would really like to ask you one more question, about the very first sample code you wrote.
    I have copied it into a new sketch, and it does work.
    The way it works is that the loop calls the (generic) has_data() function of the class. And that that function then invokes the function that actually does the work through the pointer of that class (or that instance of the class)
    What i have tried to do before i came to the forum was invoke that function through p1.hasData_x() directly from the loop.
    Tried so many ways to get the syntax: .* , ->* brackets around different parts of the function call.. always get some errors at compile
    (I have tried the same with your code, of course after making the hasData_x() public.)

    Question: is it for some technical, logical reason not allowed to call the hasData_x() from outside the class?
    And does it make any actual difference in speed.? I realized I may be looking to unrealistically shave nanoseconds off, by cutting out that generic has_data function.


    I have also just started to copy your last code into a sketch. It works even if i create an instance with

    Code:
    M1Translator translator (&Serial , &Serial);  (basically a convoluted echo sketch)
    It seems to be the shortest/best path to a working application.
    Thanks again, and since the likelihood is low, i will throw in a beer, if you ever are in Taipei.

  16. #16
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Since i now found how to include code.. Show you some test i did before asking the forum.
    I was not yet trying to Translate Serial ports , just trying to use pointers to member functions.


    In Main.cpp

    Code:
    #include <Arduino.h>
    
    #include <A_Class.h>
    
    
    void (Test::*fUN)(char* s) = &Test::Test1;
    
    Test Example2("Main example");
    char Long[20]= "Long Message ";
    char Shrt[10]= "Short M";
    char Deliver[15]= "Delivered";
    
    uint8_t Cnt=1;
    
    
    void setup() {
      Serial.begin(115200);
      delay(1000);
    
      void (Test::*fPTr)(char* s) = &Test::Test1;     // create a pointer to MemFunction named fPTr
                                                      // that returns nothin, takes a char* as argument
                                                      // and let it point to Class 'Test' function 'Test1'
    
    
      Serial.println("***SETUP************************************************************");
    
      Test myTest(" Setup example");                // myTest is an instance of class 'Test'
    
      (myTest.*fPTr)(Shrt);        // Call function on the instance (it is pointing to Test1)
    
    
      fPTr = &Test::Test2;        // let the previously declared pointer point to Test2
      (myTest.*fPTr)(Long);    // call function on the instance
    
      Serial.println("");
      Serial.println("");
    
    
    }
    
    void loop() {
      
        if (Cnt>0){
          Serial.println("******   MAIN  *********************************************************");
          Serial.print(" Count ");
          Serial.println(Cnt);
      
      
      
      // Calling the Member function through a wrapper
            My1stTest.pointr = &Test::Test1;    // Change where the pointr pointer points to
            My1stTest.CallTest(Deliver);
            
            My1stTest.pointr = &Test::Test2;
            My1stTest.CallTest(Deliver);
    
    //  CALL_MEMBER_FN(My1stTest,(fUN(Shrt));
      
      //(My1stTest.pointr)(Deliver);
    
    
    //   (My1stTest->*pointr(Shrt) );
    
    
      // Dealing with the instance of Test that is declared locally
      // Calling both functions on the same class by changing the pointer
            fUN = &Test::Test1;       // global declared function now points to Test1
            (Example2.*fUN)(Shrt);    // Example is an instance, fUN is any pointer to Test::
            delay(1000);
    
            fUN = &Test::Test2;       // Change where fUN points to
            (Example2.*fUN)(Long);    // and call fUN on Example2
            delay(2000);
    
      // Dealing with the two instances of Test that are declared in A_Class.h
            (My1stTest.*fUN)(Long);   // but the pointer is declared in Main
            (My2ndTest.*fUN)(Shrt);
    
            Cnt--;  
        }
    
    }

    In A_Class.h


    Code:
    #ifndef A_Class_H
    #define A_Class_H
    
    
    #include <Arduino.h>
    
    class Test;
    
    // typedef void (Test::*F_tst_T)(char* s);     //F_tst_T is the type name!!
                                                // A pointer of this type points to any member of Test
                                                // that cakes char* as an argument
                                                // such as Test1 and Test2
    
    #define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember))   
    
    
    
    class Test
    {
        
        typedef void (Test::*F_tst_T)(char* s);
        
        
        public:
            
            Test(const char * instName);                 // constructor
            
    
            void CallTest(char*s);
            void Test1 (char *s);
            void Test2 (char* S);
    
            F_tst_T pointr;
    
    
        private:
            uint8_t MyNum; 
            const char * _name;
    
    
    };
    extern Test My1stTest;
    extern Test My2ndTest;
    
    
    
    #endif

    in A_Class.cpp


    Code:
    #include <A_Class.h>
    
    
    Test My1stTest("ext MyFirst");
    Test My2ndTest("ext MySecond");
    
    
    
    /****************************************************************************/
    // Class Test implementation 
    /****************************************************************************/
    
    Test::Test (const char * instName){
    
        MyNum = 0;
        _name = instName;
        pointr = &Test::Test1;     // pointer now points to Test1
    
    
    }
    
    void Test::CallTest(char* msg){
        char newMsg[40];
        
        strcpy(newMsg,"instance : ");
        strcat(newMsg,this->_name);     // Place name of instance before message
        strcat(newMsg," msg ");
        strcat(newMsg,msg);
    
        
        (this->*pointr)(newMsg);
    
    }
    
    
    
    
    void Test::Test1(char* msg){
        
        Serial.println("functions TEST 1  is called ******");
        Serial.print(" with Msg = ");
        Serial.println(msg);
        Serial.print("instance Number was ");
        Serial.print(MyNum);
        
        MyNum += 5;
        Serial.print("  + 5 =   ");
        Serial.println(MyNum);
        Serial.println();
    }
    
    void Test::Test2(char* msg){
        
        Serial.println("function TEST 2  is called ***********");
        Serial.print(" msg = ");
        Serial.println(msg);
        Serial.println();
    }

    The comments in the code are how i encourage myself to understand what i am doing.

  17. #17
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    Well, i am not sure why that would be over engineered, isn't communication usually bi-directional?
    Sorry, I was unclear about that. I didn't mean the bidirectional communication, I thought that you want to generate a new object whenever you detected a input from the slave. Thinking of it, having a second translator for the answers has some charme. Somenthing like this:
    Code:
    CommandTranslator cmdTranslator(&Serial1, &Serial2); 
    AnswerTranslator ansTranslator(&Serial2, &Serial1)
    
    void setup()
    {
        Serial1.begin(115200);
        Serial2.begin(115200);
    }
    
    void loop()
    {
        cmdTranslator.tick();
        ansTranslator.tick();
    }
    If i am not bothering you yet, i would really like to ask you one more question
    As long as you keep my dinner/beer account growing I'm fine :-)

    What i have tried to do before i came to the forum was invoke that function through p1.hasData_x() directly from the loop.
    Tried so many ways to get the syntax: .* , ->* brackets around different parts of the function call.. always get some errors at compile
    (I have tried the same with your code, of course after making the hasData_x() public.)

    Question: is it for some technical, logical reason not allowed to call the hasData_x() from outside the class?
    Looks like really want to dive deeply into that member function pointer thing. Anyway, 'there's no such word as can't', here a compiling example of how to call a member function the address of which is stored in a member function pointer... (OMG)
    Code:
    void setup()
    {
       while (!Serial) ;
    
       (p1.*p1.Port::hasData_x)();
    }
    But this is really getting ugly now. If you want to do this for efficiency reasons I can just tell you it is not worth the effort. The compiler will completely optimize the call through hasData() away. Even if it wouldn't, the price for the call through hasData() is only one lookup of the function address which is usually done in 2 cycles (20ns @96Mhz) Since you are dealing with serial communication @9600 baud, the processor will probably be idle 99% of the time waiting for the next byte to arrive on the bus...

    Here a famous quote from a famous programmer
    “The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times;
    premature optimization is the root of all evil (or at least most of it) in programming.” (Donald Knuth)

  18. #18
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Dear Luni,

    The dinner/beer account has stopped growing, but i respond to you as "Dear Luni" now, as a way to show how much i appreciate all your efforts to set up a structure for what i was planning to do.

    The last question really was not because after what you have shown me, i still wanted to go with my first instinct. I just couldn't figure out if i was struggling with the syntax of the function call or that i was trying to do something illegal.
    I really like the idea of creating a derived class for every "machine" i plan to connect to a Serial port. Most of the structure will be the same, only some handling of the received data will be different, and implemented in an override function in the derived class.
    It must be simple to software engineers, i simply never considered it like that.

    I already understood that the pointer to a member function only makes sense when you talk about a specific instance of that class. (Probably because a different pointer is made for every instance you create... just didn't know how to tell the compiler )

    So, yeah, thank you so much for guiding my thoughts and pointing out what structure would make sense, and for insisting i explain more detail about what i am really trying to do.
    It has helped much more than just a code-shortcut.

    Sincerely, Oscar

    Edit: And my compliments for your very clear writing style.
    Last edited by Ozzie_G; 10-09-2019 at 04:57 AM.

  19. #19
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    You are very welcome. As you already noticed, this is one of the few friendly forums out there. So, feel free to ask if you need any support later on.

  20. #20
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    It really is a friendly forum. And it caters to tinkerers at all levels.
    I think part of it is that Paul (S) himself keeps answering questions by newbies all the time.

    :-)

  21. #21
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Well, since you insist.. LoL

    For completeness sake, and to understand the syntax, I did try your (p1.*p1.Port::hasData_x)() call in the main loop .. yep, it works.
    But then I tried to change the function that the hasData_x pointer points to (also from main loop) .
    I wrote:

    Code:
    p1.hasData_x = &(p1.hasData2);
    it compiles, it uploads, it runs
    I believe it changes the function (from the Serial monitor), but it gives me a warning.

    Code:
    src\main.cpp:28:31: warning: ISO C++ forbids taking the address of a bound member function to form a pointer to member function.  Say '&Port::hasData1' [-fpermissive]
       p1.hasData_x = &(p1.hasData1);

    Several other syntax i tried fail to compile with error messages:


    Code:
    p1.hasData_x = &(p1.*p1.hasData2);
    
    
    src\main.cpp:37:27: error: 'p1.Port::hasData2' cannot be used as a member pointer, since it is of type '<unresolved overloaded function type>'

    I have a folder with all kinds of sketches that do virtually nothing, but that i keep for syntax reference, mostly dealing with loops, strtok and other string copying and testing, float precision testing, fprint examples, bitwise manipulation, and importantly, pointers.
    I will save these examples you wrote there as well.

    btw.
    I am using the TyCommander that you used to show your code working. Seems like a powerful tool.
    Have you heard of, or used DockLight before? It is great tool for testing serial port code.
    (Free to use with limitations)
    Last edited by Ozzie_G; 10-09-2019 at 06:40 AM. Reason: additional

  22. #22
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    578
    The compiler is very friendly to you and tells you exactly what it wants you to do:
    Code:
    src\main.cpp:28:31: warning: ISO C++ forbids taking the address of a bound member function to form a pointer to member function.  Say '&Port::hasData1' [-fpermissive]
       p1.hasData_x = &(p1.hasData1);
    So following its advice you get a happy compiler and a working code:

    Code:
    void setup()
    {
       while (!Serial) ;
    
       p1.hasData_x = &Port::hasData1;
       (p1.*p1.Port::hasData_x)();
    
       p1.hasData_x = &Port::hasData2;
       (p1.*p1.Port::hasData_x)();
    }
    (you need of course make hasData1/2 public first) But: the whole point of OOP is to hide away implementation details. Setting pointers to member functions of a class from outside of the class is somehow exactly the opposite of this idea.
    Last edited by luni; 10-09-2019 at 07:23 PM.

  23. #23
    Junior Member
    Join Date
    Mar 2013
    Location
    Taipei
    Posts
    17
    Are you saying the compiler AND the Forum are friendly?
    Lucky me.

    I did try to follow the suggestions of the error message, but never could come up with

    Code:
    p1.hasData_x = &Port::hasData1;
    I guess, in hindsight, it is only logical that we don't need to use p1 in the statement on the righthand side. We wouldn't expect to set the pointer to a function in another instance of the class.

    Yeah, your comment about the purpose of OOP makes perfect sense.
    Let me tell you i had written code for this machine problem before, without using classes, because those are hard for absolute beginners. (I am a physics major myself, software is just required sometimes)
    And it kinda works.
    It used Strings as command buffers, I write code for one serial port on teensy, then literally copy-pasted it and changed all variables to 'somevariable_2' , for the second port. The code would compile for Teensy 3.1, but was too big for a Teensy3.0 (unless i used the smaller code option in the compiler)
    At one point, i re-wrote all code to use char[] and use pointers to them in function calls (but kept the overall structure of the sketch). The code shrank to almost half i believe.
    It was still spaghetti code. I could not remember what parts of the code where for.
    In the mean time i started reading other people's libraries for Arduino, these use classes.
    For one project, (a clock, oh Arduino beginners everywhere) I use a Menu library to display a small user menu on a display, it has a base class and several derived classes. Didn't understand a single line.
    But slowly....

    So now i want to attempt to re-write the code again for flexibility. The idea of hiding implementation is perhaps still a bit advanced for me. Not a priority.
    I recognized that using a class makes sense. But didn't think about an abstract class and a derived class for specific machines on specific ports. I wanted every instance of the class to be able to handle all possible ports.
    You can't fake experience or actual software engineer's education, i spose.

    This will be it from me, for a long while, one hopes.
    Thanks for all your help.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •