1

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

sehe
  • 374,641
  • 47
  • 450
  • 633
Simon
  • 325
  • 1
  • 6
  • 1
    Please edit your question to provide a [mcve] that can be used to reproduce the problem. Also include any error message verbatim. – G.M. Jul 05 '21 at 18:02
  • Unfortunately I can not provide a minimal working example because the objects I use external numerical libraries. Maybe I can write a piece of dummycode where I can reproduce the problem. But is that what I am trying to achieve possible at all? – Simon Jul 05 '21 at 19:40
  • Yes, please do write "dummycode". Chances are that the process of writing that code will help you find your problem, and it will at least make your question clearer. – jjramsey Jul 06 '21 at 13:03

1 Answers1

2

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?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you very much for your detailled answer! :-) The answer to the puzzle is that actually Solid is the owner of the signal (a member variable of Solid actually). Thus I want to track to lifetime of the pointer to Other Class. Btw, I could fix my posted code by replacing "pointer_M.get()" with "this", because I want to connect a member function of Solid. I guess you are right; I would not a shared_ptr. My pointer_M is actually the only pointer to the object of Other Class. So a unique_pointer would be a better choice, is it? – Simon Jul 07 '21 at 09:05
  • I'd suggest that a already member already expresses ownership. So, you might use unique pointers if the lifetime should not overlap 100% with the Solid instance, but consider more expressive containers like `std::optional<>` in such a case. – sehe Jul 07 '21 at 10:25
  • I'd usually use a simple data member to express the ownership relation. If the lifetime needs to be varied then yes `unique_ptr` might make sense, though I'd consider `std::optional<>` to express the lazy lifetime semantics without implying dynamic allocation. – sehe Jul 07 '21 at 10:32
  • Oh. Blimey. I wrote that comment in a different tab. I thought I lost it :) – sehe Jul 07 '21 at 10:33
  • One additional question to that: Is the boost::shared_ptr "aware" of possible changes to the object he points to? Let's say the boost pointer points to an object which stores the address of a variable, which is a member of the solid instance as well. Now the solid instance changes this member (maybe also its address). Does the boost pointer automatically also updates this address or do the addresses of that member in the two instances now differ? – Simon Jul 07 '21 at 16:04
  • The latter. You can of course use the aliasing constructor to express this kind of complicated relations, but in practice I've never found a scenario where it wasn't simpler to decouple the ownership graph to avoid the cycles. And believe me, I've been tempted more than enough times to go "Gordian Knot" with these. I recommend to look for the simple path. Future you will thank you. (And so will your co-workers if you have them) – sehe Jul 07 '21 at 16:40
  • Thank you. Is it a big effort to change my pointerM so that he actually keeps track of possible changes that the Solid instance does? Could you show me how to do that? :-) – Simon Jul 07 '21 at 16:51
  • Not unless you get a lot more specific. Maybe you should - again - make a dummy example demonstrating the ask, and we may try. (For clarity: I'm suggesting a separate question if you choose to do that) – sehe Jul 07 '21 at 18:09