Boost::signal2 - удаление объекта со слотом

Учти это:

#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();
}

Выход "Слот называется!" и никаких аварий или чего-либо еще. Вот почему у меня есть несколько вопросов:

1) Почему нет аварии?

2) Почему не происходит сбой, даже если функция слота назначает объект, который не существует?

3) Как я могу сделать так, чтобы сигнал автоматически отслеживал время жизни его слотов? Я имею в виду, когда слот разрушен, он отключается.

Вопрос № 3 является наиболее важным, так как мне нужно реализовать шаблон наблюдателя, и очень часто время жизни наблюдателей (слотов) не будет статичным (в течение всего времени, когда приложение работает).

3 ответа

Решение

1) Тебе повезло. Если нет, вы получите ошибку сегментации.

2) Память никак не перезаписывалась.

3) Вы можете использовать slot:: track для автоматического отключения, когда отслеживаемый объект удаляется. Boost.Signals2 может отслеживать объекты, которые управляются 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;
}

ОБНОВИТЬ:
Добавлен код для отслеживания объектов для std::shared_ptr и 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 и 2) На самом деле это неопределенное поведение. Вы использовали оператор разыменования, теперь connect имеет значение object_with_slot, его адрес может быть назначен менеджером памяти любому другому процессу. По совпадению это все еще "действительный адрес". И ptr может быть назначен любому другому значению без утечки памяти.

Попробуйте что-то вроде этого, и вы увидите, что каждый раз взрывается

#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) Вы можете поставить другой сигнал на деструктор object_with_slot, после чего он может уведомить, когда он вызывается.

Приводятся очень опасные примеры. Взглянуть:

#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;
}

Как вы думаете, из чего выходит этот код (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

Я не думаю, что такое поведение было ожидаемым. Потому что мы передаем копию (по значению) *ptr (экземпляр object_with_slot) к connect метод. Это может быть решено, например, с помощью упаковщиков ссылок:

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

Будьте осторожны с шаблонами и типами.

Другие вопросы по тегам