Search code examples
c++boostsignals2

boost::signals2: connect to a slot of a diffeent class


I am trying to use the boost::signals2 functionalities in my program. In my "Main Class" called "Solid" I have a member which I initialize inside the constructor body (so after the member initializer list) like this:

pointer_M = boost::make_shared<OtherClass>(/*parameters,...*/);

If the signal is triggered by some function I do not want to call a member of "OtherClass" (to which pointer_M points) but a member from "Solid", i.e. the class which initialized pointer_M just.

I tried the following:

 boost::signals2::connection tria_signal = /*...*/.connect( boost::signals2::signal<void()>::slot_type(&Solid::call_this_function, pointer_M.get()).track(pointer_M) );

"call_this_function" is a member function of Solid. Unfortunately there are a bunch of error messages. "OtherClass" and "Solid" are not related by inheritance.

I would appreciated getting some advice how to fix my issue since I am very unexperienced with boost.

Best


Solution

  • But is that what I am trying to achieve possible at all?

    The point is we can't tell without a clearer description. It sure sounds like a no-brainer: signals are specifically used to decouple callers and callees.

    So let me just make up your "dummycode" for you. I'm going to sidestep the enormous type-overcomplication that you showed in that line:

    boost::signals2::connection tria_signal =
    /*...*/.connect(boost::signals2::signal<void()>::slot_type(
                    &Solid::call_this_function, pointer_M.get())
                    .track(pointer_M));
    

    The whole idea of slots is that they generalize callables using type erasure. Just provide your callable in any compatible form and let the library deduce the arcane implementation types.

    Live On Coliru

    #include <boost/signals2.hpp>
    #include <boost/make_shared.hpp>
    #include <iostream>
    
    struct OtherClass {
        OtherClass(...) {}
    
        boost::signals2::signal<void()> tria;
    
        void test() {
            if (!tria.empty())
                tria();
        }
    };
    
    struct Solid {
        boost::shared_ptr<OtherClass> pointer_M;
    
        Solid() {
            pointer_M = boost::make_shared<OtherClass>(1,2,3);
            auto tria_signal = pointer_M->tria.connect(
                boost::bind(&Solid::call_this_function, this));
        }
    
      private:
        void call_this_function() {
            std::cout << "Solid was here" << std::endl;
        };
    };
    
    int main() {
        Solid s;
        s.pointer_M->test();
    }
    

    Prints

    Solid was here
    

    Crystal Ball: Can We Guess The Problem?

    Maybe we can guess the problem: it looked like you were putting effort into tracking the lifetime of the object pointed to by pointer_M. That's not useful, since that OtherClass owns the signal in the first place, meaning that all connections are disconnected anyways when the OtherClass disappears.

    Correct Lifetime Management

    What you likely want is for the connection to disconnect when the lifetime of the Solid object ends, not the OtherClass. In general, I'd suggest using scoped_connection here:

    Live On Coliru

    #include <boost/signals2.hpp>
    #include <boost/make_shared.hpp>
    #include <iostream>
    
    struct OtherClass {
        OtherClass(...) {}
    
        boost::signals2::signal<void()> tria;
    
        void test() {
            if (!tria.empty())
                tria();
        }
    };
    
    struct Solid {
        boost::shared_ptr<OtherClass> pointer_M;
    
        Solid() {
            pointer_M = boost::make_shared<OtherClass>(1,2,3);
            tria_signal = pointer_M->tria.connect(
                boost::bind(&Solid::call_this_function, this));
        }
    
      private:
        boost::signals2::scoped_connection tria_signal;
    
        void call_this_function() {
            std::cout << "Solid was here" << std::endl;
        };
    };
    
    int main() {
        boost::shared_ptr<OtherClass> keep;
        {
            Solid s;
            std::cout << "Testing once:" << std::endl;
            s.pointer_M->test();
    
            keep = s.pointer_M; // keep the OtherClass alive
        } // destructs Solid s
    
        std::cout << "Testing again:" << std::endl;
        keep->test(); // no longer connected, due to scoped_connection
    }
    

    Prints

    Testing once:
    Solid was here
    Testing again:
    

    Simplify

    In your case, the OtherClass is already owned by the Solid (at least it is created). It seems likely that having the shared-pointer is not necessary here at all:

    Live On Coliru

    #include <boost/signals2.hpp>
    #include <boost/make_shared.hpp>
    #include <iostream>
    
    struct OtherClass {
        OtherClass(...) {}
    
        boost::signals2::signal<void()> tria;
    
        void test() {
            if (!tria.empty())
                tria();
        }
    };
    
    struct Solid {
        Solid() : oc_M(1,2,3) {
            tria_signal = oc_M.tria.connect(
                boost::bind(&Solid::call_this_function, this));
        }
    
        void test() { oc_M.test(); }
    
      private:
        OtherClass oc_M;
        boost::signals2::scoped_connection tria_signal;
    
        void call_this_function() {
            std::cout << "Solid was here" << std::endl;
        };
    };
    
    int main() {
        Solid s;
        s.test();
    }
    

    Because the members are destructed in reverse order of declaration, this is completely safe.

    Architecture Astronauting

    If you know what you're doing, and the pointer_M actually needs to be shared, then likely you want to track that pointer. You should probably be considering making Solid also enable_shared_from_this. If you want to be really "Enterprise Grade Engineer™" about it, you could perhaps do something fancy with the aliasing constructor: What is shared_ptr's aliasing constructor for?