Вопрос

Consider this:

#include <boost/signals2.hpp>
#include <iostream>

struct object_with_slot
{
void operator()()
{
   std::cout << "Slot called!" << std::endl;
   member = 50500;
}
int member;
};


int main()
{
boost::signals2::signal<void ()> sig;

object_with_slot * ptr = new object_with_slot;
sig.connect(*ptr);

delete ptr;

sig();
}

Output is "Slot called!" and no crash or anything. That's why I have a few questions:

1) Why there is no crash?

2) Why there is no crash even if the slot function assigns something to object which doesn't exist?

3) How can I make the signal automatically track the lifetime of its slots? I mean when the slot is destroyed, it gets disconnected.

The question number 3 is the most important, as I need to implement observer pattern and very often lifetime of observers (slots) won't be static (for the whole time when app is running).

Это было полезно?

Решение

1) You're lucky. If not, you'll get a segmentation fault.

2) The memory was not overwritten in any way.

3) You could use slot::track to automatically disconnect when the tracked object gets deleted. Boost.Signals2 could track objects that are managed by boost::shared_ptr.

#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>

struct object_with_slot
{
    void operator()()
    {
       std::cout << "Slot called!" << std::endl;
       member = 50500;
    }
    int member;
};

//
int main()
{
    typedef boost::signals2::signal<void ()> sig_type;
    sig_type sig;

    {
        boost::shared_ptr<object_with_slot> ptr(new object_with_slot);
        sig.connect(sig_type::slot_type(*ptr).track(ptr));

        // 'object_with_slot' managed by ptr is destroyed
    }

    sig(); // 'object_with_slot' not called here.

    return 0;
}

UPDATE:
Added code to track objects for std::shared_ptr and std::weak_ptr:

#include <memory>
#include <boost/signals2.hpp>

// added specializations for std::weak_ptr and std::shared_ptr
namespace boost
{
  namespace signals2
  {
    template<typename T> struct weak_ptr_traits<std::weak_ptr<T> >
    {
      typedef std::shared_ptr<T> shared_type;
    };

    template<typename T> struct shared_ptr_traits<std::shared_ptr<T> >
    {
      typedef std::weak_ptr<T> weak_type;
    };
  }
}

struct object_with_slot
{
    void operator()()
    {
       std::cout << "Slot called!" << std::endl;
       member = 50500;
    }
    int member;
};

//
int main()
{
    typedef boost::signals2::signal<void ()> sig_type;
    sig_type sig;

    std::shared_ptr<object_with_slot> ptr(new object_with_slot);
    sig.connect(sig_type::slot_type(*ptr).track_foreign(ptr)); // ptr is tracked

    sig();

    return 0;
}

Другие советы

1 and 2) In fact it is an undefined behaviour. You employed the dereference operator, now connect has the value of the object_with_slot, its address is free to be assigned by memory manager to any other process. By coincidence it is still a "valid address". And ptr is free to be assigned to any other value without cause memory leak.

Try something like this and you will see that explodes everytime

#include <boost/signals2.hpp>
#include <iostream>

struct object_with_slot
{
    object_with_slot()
    {
        member = new int(10);
    }

    ~object_with_slot()
    {
        delete member; //comment this line and everything works again
    }
    void operator()()
    {
        std::cout << "Slot called!" << std::endl;
        *member = 50500; //it was destroyed above
    }
    int *member;
};


int main()
{
    boost::signals2::signal<void ()> sig;

    object_with_slot * ptr = new object_with_slot;
    sig.connect(*ptr);

    delete ptr;
    ptr = 0x0;

    sig();
}

3) You can put another signal on destructor of object_with_slot, then it can notify when it is called.

Very dangerous examples are given. Take a look:

#include <iostream>
#include <memory>
#include <boost/signals2.hpp>

struct object_with_slot
{
    object_with_slot() {
        std::cout << "ctor\n";
    }

    object_with_slot(const object_with_slot &) {
        std::cout << "cctor\n";
    }

    ~object_with_slot() {
        std::cout << "dtor\n";
    }

    void operator()()
    {
       std::cout << "Slot called!" << std::endl;
       member = 50500;
    }
    int member;
};

//
int main()
{
    typedef boost::signals2::signal<void ()> sig_type;
    sig_type sig;

    std::shared_ptr<object_with_slot> ptr(new object_with_slot);
    sig.connect(sig_type::slot_type(*ptr).track_foreign(ptr)); // ptr is tracked

    sig();

    return 0;
}

How do you think, what does this code out (g++ 4.8.1, libboost 1.54)?

ctor
cctor
cctor
cctor
cctor
cctor
cctor
cctor
cctor
dtor
dtor
dtor
dtor
dtor
cctor
dtor
cctor
dtor
dtor
dtor
cctor
dtor
Slot called!
dtor
dtor

I don't think, that this behaviour was expected. Because we pass copy (by value) of *ptr (instance of object_with_slot) to the connect method. It might be solved, for example, by reference wrappers:

sig.connect(sig_type::slot_type(boost::ref(*ptr)).track_foreign(ptr)); // ptr is tracked

Be careful with templates and types.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top