Using 2-way communication between objects

I originally posted this in "Beginners", but maybe it would be better fit to be here. That and "Beginners" is getting flooded with some guy making tons of spam accounts.

I apologize if this is a stupid question or something that I should be able to do easily.

I'm working on something where I have a collection of objects (of different types, but all derived from the same virtual base class).
It sort of has this structure:

1) Object in collection receives some input it can't handle
2) Object sends a "signal" to the containing object
3) Containing object handles "signal"
4) Containing object informs the sending object of either a solution or failure to solve

I'm stuck at 4. Right now, the object have in them a function pointer, which takes a "signal" to send to the containing object. Really, it's just passing a pointer to its own internal storage of a "signal", since there's something about using function pointers with arguments that I don't understand.

The signals are sent up and handled properly, but I can't send them back down. When the containing object receives a signal, it does via a function pointer (std::function), the sole argument being a pointer to the signal sent. The declaration is:

 
void ReceiveSignal(Signal::Signal *);


When this object is added to the containing object's list, this is the code that does it:

 
element->sendSignal = std::bind(&Parent::ReceiveSignal, this, element->sig);


When the signal gets sent, it looks like this:

1
2
*sig = Signal::Error; //change interval signal value
sendSignal(sig); //Send pointer to internal signal value - I know this is bad 


element is a pointer to the object being added. "sendSignal" is a std::function stored in the sending object; It is initially bound to a no-action function, but that's just because I wan't to be safe.

I know there are multiple things wrong with what I just did.

Using "element->sig", which is a pointer to a "signal" inside of the sending object, as a placeholder when binding is not the right way to handle sending parameters in std::function. Without doing that, every call to this bound function outputs the same value, regardless of what value I actually put in the parenthesis.

I guess my main question is:

What is the best, safest way to do 2-way callbacks? It might seem a little counter-intuitive.
I want the containing object to send the "signal" to the containing object. That containing object takes some action and sends a resolution "signal" back to the object that originally sent that message.

The way I did it feels very "hacky", and it doesn't seem like good programming practice. I could just use function pointers in C, but they don't quite achieve what I'm going for, as the collection of object can be a collection of any number of different types of items, but they're all derived from the same base class. That base class has the "sendSignal" function pointer in it, as a public variable. The containing object won't know anything about the properties of the derived version that is actually in the container, as it is a list of pointers of the base class object type, but that's how I designed it.

Basically, I need a callback registered for each object in both directions. But the way I designed it was that the main object doesn't know anything about the object in the container except that it at least has everything that the virtual base class does.

Perhaps it would help if I gave a UML diagram.

https://i.imgur.com/5dLgeMN.jpg

Of course, I don't need someone to write all of the code for me. I'd really like some feedback on how to keep the class structure I've develop must establish some way for the Main Object to somehow remember which of its contained objects sent the signal. When each object is added to the Main Object's list, their "sendSignal" function is bound to MainObject, so every single one of the contained objects will call the same function when they send a signal.

This bad programming practice of mine must be eliminated.
this is the code that does it:
If you want to pass a parameter that way you need 'placeholders':

http://www.cplusplus.com/reference/functional/bind/?kw=bind


When the signal gets sent, it looks like this:
sig is not passed to the function.
> I want the containing object to send the "signal" to the containing object. That containing object
> takes some action and sends a resolution "signal" back to the object that originally sent that message.

Something along these lines, perhaps:
The signal send to the containing object: forward via a polymorphic call wrapper
Signal back to the object that that originally sent that message: call a virtual function declared in the base class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include <iomanip>
#include <memory>

struct signal_data { std::string id = "no id" ; bool handled = false ; /* .... */ };

struct contained_object_base
{
    using signal_handler_type = std::function< void( contained_object_base& sender, signal_data info ) > ;

    virtual ~contained_object_base() noexcept = default ;
    contained_object_base() = default ;
    contained_object_base( const contained_object_base& ) = default ;
    contained_object_base( contained_object_base&& ) noexcept = default ;
    contained_object_base& operator= ( const contained_object_base& ) = default ;
    contained_object_base& operator= ( contained_object_base&& ) noexcept = default ;

    void signal_handler( signal_handler_type handler ) { signal_recvr = handler ; }

    void send_signal( signal_data sd ) { do_send_signal(sd) ; }
    void post_reply( signal_data sd ) { do_post_reply(sd) ; }

    private:
        virtual void do_send_signal( signal_data sd )
        {
            if(signal_recvr)
            {
                std::cout << "object at address " << this << ": sending signal "
                          << std::quoted(sd.id) << " to signal handler\n" ;
                signal_recvr( *this, sd ) ;
            }
        }

        virtual void do_post_reply( signal_data sd ) = 0 ;

        signal_handler_type signal_recvr ;
};

struct container_object
{
    void add_item( contained_object_base& obj )
    {
        collection.push_back(obj) ;
        using namespace std::placeholders ;
        obj.signal_handler( std::bind( &container_object::handle_signal, this, _1, _2 ) ) ;
    }

    std::vector< std::reference_wrapper<contained_object_base> > collection ;

    void handle_signal( contained_object_base& sender, signal_data info )
    {
        std::cout << "container_object::handle_signal " << std::quoted(info.id) << " sender: "
                  << typeid(sender).name() << " at address " << std::addressof(sender) << '\n' ;
        //
        info.handled = true ;
        sender.post_reply(info) ;
    }
};

int main()
{
    struct my_object : contained_object_base
    {
        private: virtual void do_post_reply( signal_data sd ) override
        {
            std::cout << "main::my_object at address " << this << " : recd reply for signal "
                      << std::quoted(sd.id) << " handled? " << std::boolalpha << sd.handled << '\n' ;
        }
    };

    constexpr std::size_t N = 5 ;
    my_object objects[N] ;

    container_object cntr ;
    for( auto& obj : objects ) cntr.add_item(obj) ;

    const std::string signal_name[N] { "alpha", "beta", "gamma", "delta", "epsilon" } ;
    std::size_t n = 0 ;
    for( auto& obj : objects )
    {
        obj.send_signal( { signal_name[ (n++)%5 ] } ) ;
        std::cout << '\n' ;
    }
}

http://coliru.stacked-crooked.com/a/e3270383a4d57fcf
Do you really need a function pointer? Why not make ReceiveSignal a virtual method?

Do you really need to have the receiving object send a signal back to the sending object? Why not just give ReceiveSignal a return value?

If the receiving object always sends a signal back each time it receives one, then you have to be careful to prevent infinite recursion: A sends to B which responds to A which responds to B...
@coder777
I've just realized that I've not used placeholders properly. I'll have to update that. I never use "using namespace", so I sometimes miss stuff.

@JLBorges
That looks like exactly what I want. I'll have to spend some time looking over that code to make sure I understand what it going on. But it looks like it also keeps the class structure that I want, in that contained objects need know nothing about the container object except that it is derived from the base class.

@dhayden
I suppose I don't have to use function pointers. It was just all I could think of at the moment. I'm always open to using something else.
I would make it a return value, but my application is real-time, so the signal receipt/sending has to be non-blocking. The object that originally sent the message can't do anything else until it is handled, but other objects may need to continue working in real time while the original object's problem is dealt with.
And thanks for the tip. I'm not always great at noticing a possible infinite recursion problem.
There are actually only 3 "levels" in which the signals are sent.
A is the origin, B is the middle, which may or may not have an immediate solution, and C, which is the top, which will have "extreme" solutions.
A can send to B, B can sent to A and C, and C can send to B. I've tried to make sure that A can't ever respond to signals, only send them and wait for a response. There may be a problem with B and C. I'll look over code to make sure I don't get a recursion issue there.

Thank you all for the advice! After playing with this a bit and seeing if I can make it integrated into my project, I may be back with more questions, or clarifications.
> Why not make ReceiveSignal a virtual method?

Best to design an object such that it does not have a dependency on the interface of a container which may possibly contain the object.

Best to avoid cyclic dependencies.
@JlBorges

Just curious, is your code an implementation of the Observer Design Pattern?

Regards :+)

Alright, now that I've had some coffee and some time to poke through this, I think I've got the idea. I guess the main point of the Main Object having just a set of pointers to the base class instead of specific derivations is that whoever uses the software can code their own derivative object to do whatever they want and have it integrate just like every other derivative object. The base class has a set of functions, both virtual and not virtual (some will always do the same thing on every call, no matter what, so there's no point in forcing derivative objects to override it), and the Main Object can use what the base class provides to interface with the objects in its container. Any special functionality is handled inside the derivative object itself. There isn't any functionality (yet) of Main Object where it needs to know anything about the derived version of the class.

I can see one problem with my project - it is single-threaded. So any instance of infinite recursion, or waiting for a response as a return value, would render the software completely unresponsive and unusable. Other parts of the software need to continue working. That's why I initially wanted to use signal sending. Have the object that is having the problem send the signal, and that single object can halt and wait. It will actually be "disabled" and marked as "waiting for response" (Main Object has the ability to "enable" and "disable" contained objects if it deems it necessary). While the disabled object is halted, however, everything else needs to run. The top level, which actually does some pretty critical stuff, can't be blocked or the whole thing gets blocked. So, I just wanted to design it in such a way that the sending object sends a signal and returns from that signal call, but in a "disabled" state. The Main Object and the highest level may take time to either get a solution from the software user or devise their own solution, or could even ask other contained objects to handle it if for some reason that was designed into the signal sender. But once the issue was resolved, a signal would be sent back to the original sender telling it what to do and how to proceed.

I know it would be best to do this using multi-threading, but making everything thread-safe is something I'm still learning how to do (still in college here), and all the multi-threading we've done is on Linux. My project is on Windows (please forgive me). I'll be looking into incorporating threading into my project once it reaches an "alpha" state, at which time I may come here and ask for input/feedback.


This project is personal, not school related, so don't worry about that. There's no deadline, and I'm not "freaking out". This is my attempt at building a system that I've actually always wanted to build, but just now have the knowledge (or almost all the knowledge) to do so. Before this, I either haven't had the motivation (since resolved issues there) or didn't have the knowledge. Now I feel confident that I do.

I really do appreciate all the feedback you all have to give, as I'm sure you give a lot of feedback that gets no appreciation. This project, however weird it may sound, is very important to me, so every step closer I get to improving it is a leap in my book. Once my project reaches an "alpha", perhaps I'll open the GitHub repo and let you all look at it. But be nice when giving criticism on it :)
> is your code an implementation of the Observer Design Pattern?

No. The observer pattern "defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified"
http://www.oodesign.com/observer-pattern.html

The observed object maintains a collection of observers.

Something along these lines, in idiomatic C++ (read non-java-like, non-qt-like etc.).
(Note: Unregistering of observers elided for brevity).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <functional>
#include <vector>
#include <string>

struct observed_object
{
    using observer = std::function< void( const observed_object& ) > ;

    template < typename FN, typename... EXTRA_ARGS > void register_observer( FN&& fn, EXTRA_ARGS&&... extra_args )
    {
        using std::placeholders::_1 ;
        observers.emplace_back( std::bind( std::forward<FN>(fn), _1, std::forward<EXTRA_ARGS>(extra_args)... ) ) ;
    }

    int value() const { return val ; }
    void value( int v ) { if( val != v ) { val = v ; report_value_changed() ; } }

    private:
        std::vector<observer> observers ;
        int val = 0 ;

        void report_value_changed() { for( auto& fn : observers ) fn(*this) ; }
};

int foo( const observed_object& sender, int a, int b )
{
    std::cout << "foo - sender: " << std::addressof(sender) << " a: " << a << " b: " << b << '\n' ;
    return sender.value() + a + b ;
}

int main()
{
    observed_object obj ;
    const auto handler = []( const observed_object& sender, std::string info )
    { std::cout << "closure - sender: " << std::addressof(sender) << " info: " << info << '\n' ; } ;

    obj.register_observer( handler, std::string("hello") ) ;
    obj.register_observer( foo, 23, 9456 ) ;

    struct an_observer
    {
        void handle_value_changed( const observed_object& sender ) const
        { std::cout << "an_observer::handle_value_changed - sender: " << std::addressof(sender) << '\n' ; }
        // ...
    };

    an_observer this_observer ;
    obj.register_observer( std::bind( &an_observer::handle_value_changed, std::ref(this_observer),
                                      std::placeholders::_1 ) ) ;

    obj.value(10) ;
}

http://coliru.stacked-crooked.com/a/3cbd4dbe68aa7824
Last edited on
@JLBorges

Wow, thanks for that. I will file that along with all the other code snippets you post :+)

Thanks for the extra effort you put in, you are a great asset to this forum.

Regards
I did warn you all that I would have plenty of questions. Instead of posting a wall of text trying to explain my architecture, I'd rather post a visual representation. I'm a very visual person, so drawing it out often helps me - this is how I plan most of my software development from the ground up. Sketches of class interactions/abilities and design my software layers from there.

Class diagram: http://i.imgur.com/T948qEm.jpg

Interaction and Signal Diagram: http://i.imgur.com/MzMy7fY.jpg

This has sort of made me question my decision to implement this as a layered architecture. I'll continue development as a layered architecture, as it is the only architecture I know that allows me to enforce communication paths and restrictions. If you have feedback on my architecture choice, I'd be happy to hear it. Once I get this developed far enough, I'll open up the source code for others to see. I'm working on a wiki for it as it is developed.
Plenty of people have told me to not try to develop these kinds of software, but I'm going to try anyway. The only way I can further develop my skills is to continue to learn and practice.
I'm not really wanting to open up the code until it is far enough along that no one can really tell me to give up or stop working on it because "it's been done before" or "others have done it better than you ever could". It's something I want to do, so I'll develop it.

I chose to have both the Supervisor and the Worker be derived from the same class so that the Supervisor at least knows about the workings of the base class, and it can interact with it more easily. The supervisor just has added functionality so that it can do things like forcefully delete a worker, disable signal sending, forcefully disable - basically limit or suspend functionality of whatever the offending object it.
I apologize for not marking this as solved.

Since I have, for some reason, insiststed on using Visual Studio, I continually have issues using standard-compliant code that should work but doesn't because VS doesn't support some features.
But I have come up with another method, but I will be working towards implementing a polymorphic solution after finding out how I have to do it in VS.

Thank you all for your feedback. I really appreciate it. Work is still being done on it, so every day I get closer. Once it reaches an alpha state, I'll open the repo for others to see if they wish.
Topic archived. No new replies allowed.