LINUX.ORG.RU

Вызвать метод базового класса при разрушении дочернего объекта

 , , ,


1

1

По следам темы eao197 слепил свою модель Ad-hoc легковесных недоакторов с помощью boost::asio::io_service и boost::signals2. Вот что получилось:

#include <iostream>
#include <mutex>
#include <thread>
#include <atomic>
#include <boost/asio/io_service.hpp>
#include <boost/signals2.hpp>
#define BOOST_TEST_MODULE Miscellaneous
#include <boost/test/unit_test.hpp>

namespace tests {

std::ostream&
print_one(std::ostream& os)
{
    return os;
}

template <class A0, class... Args>
std::ostream&
print_one(std::ostream& os, const A0& a0, const Args&... args)
{
    os << a0;
    return print_one(os, args...);
}

template <class... Args>
std::ostream&
print(std::ostream& os, const Args&... args)
{
    std::ostream& result = print_one(os, args...);
    os.flush();
    return result;
}

template <class... Args>
std::ostream&
print(const Args&... args)
{
    static std::mutex m;
    std::lock_guard<std::mutex> lg(m);
    return print(std::cout, args...);
}

class BaseActor {
public:
    BaseActor()
        : thread_()
    {
    }
    virtual ~BaseActor()
    {
        stop();
    }

    void start()
    {
        stop();
        io_service.reset();
        work_.reset(new boost::asio::io_service::work(io_service));
        std::thread(std::bind(&BaseActor::threadFunc_, this)).swap(thread_);
    }

    void stop()
    {
        work_.reset();
        io_service.stop();
        if (thread_.joinable())
            thread_.join();
    }

public:
    boost::asio::io_service io_service;

protected:
    virtual void threadFunc_() { io_service.run(); };

protected:
    std::thread thread_;
    std::unique_ptr<boost::asio::io_service::work> work_;
};

class ActorA : public BaseActor {
public:
    std::function<void(const int&)> getDoJobAInvoker()
    {
        return io_service.wrap(std::bind(&ActorA::doJobA_, this, std::placeholders::_1));
    }

    ~ActorA() { stop(); };

public:
    boost::signals2::signal<void(const std::string&)> helloSig;
    std::atomic_int aNumber{ 0 };

protected:
    virtual void threadFunc_()
    {
        print("ActorA has started", "\n");
        io_service.run();
        print("ActorA has finished", "\n");
    };

private:
    void doJobA_(const int& num)
    {
        print("ActorA::doJobA_() from thread ", thread_.get_id(), "\n");
        print("num = ", num, "\n");
        std::ostringstream oss;
        oss << "Hello, World " << num << "!";
        helloSig(oss.str());
        aNumber.store(num);
    }
};

class ActorB : public BaseActor {
public:
    std::function<void(const std::string&)> getDoJobBInvoker()
    {
        return io_service.wrap(std::bind(&ActorB::doJobB_, this, std::placeholders::_1));
    }

    ~ActorB() { stop(); };

public:
    boost::signals2::signal<void(const int&)> intSig;

protected:
    virtual void threadFunc_()
    {
        print("ActorB has started", "\n");
        io_service.run();
        print("ActorB has finished", "\n");
    };

private:
    void doJobB_(const std::string& str)
    {
        print("ActorB::doJobB_() from thread ", thread_.get_id(), "\n");
        print("str = ", str, "\n");
        intSig(++cout);
        aString_ = str;
    }

    int cout = 0;
    std::string aString_;
};

BOOST_AUTO_TEST_CASE(BoostIOServiceTest)
{
    print("Main thread id is ", std::this_thread::get_id(), "\n");

    ActorA actorA;
    ActorB actorB;
    actorA.helloSig.connect(actorB.getDoJobBInvoker());
    actorB.intSig.connect(actorA.getDoJobAInvoker());
    actorA.start();
    actorB.start();
    actorB.intSig(0);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

} // namespace tests

Этот код, разумеется, работает, и работает как положено, но есть у него недостаток: необходимо в деструкторах дочерних классов явно вызывать метод stop() базового класса, иначе велика вероятность, что при разрушении вызовется уже уничтоженный к этому времени сигнал, определенный в дочернем классе, а только потом остановится поток, который этот сигнал вызывает. Отсюда вопрос: как можно избавиться от необходимости явно вызывать BaseActor::stop() из деструкторов ActorA и ActorB?

★★★★★
Ответ на: комментарий от asaw

а CRTP уже обсуждалось выше.

Да не нужен он тут. Вернее его можно добавить опционально.

anonymous
()
Ответ на: комментарий от anonymous

Очень даже в тему. Вместо того, чтобы вводить тут наследование, нужно ввести класс actor, который бы хранил и управлял временем жизни твоих акторов. И тогда вопросов вроде сабжа возникать не будет.

Подобное решение уже предлагалось: Вызвать метод базового класса при разрушении дочернего объекта (комментарий)

asaw ★★★★★
() автор топика

По итогам обсуждения получился такой вот вариант с фабрикой для слотов:

#include <thread>
#include <atomic>
#include <iostream>
#include <mutex>
#include <sstream>
#include <boost/asio/io_service.hpp>
#include <boost/signals2.hpp>

std::ostream&
print_one(std::ostream& os)
{
    return os;
}

template <class A0, class... Args>
std::ostream&
print_one(std::ostream& os, const A0& a0, const Args&... args)
{
    os << a0;
    return print_one(os, args...);
}

template <class... Args>
std::ostream&
print(std::ostream& os, const Args&... args)
{
    std::ostream& result = print_one(os, args...);
    os.flush();
    return result;
}

template <class... Args>
std::ostream&
print(const Args&... args)
{
    static std::mutex m;
    std::lock_guard<std::mutex> lg(m);
    return print(std::cout, args...);
}

class BaseActor {
public:
    virtual ~BaseActor()
    {
        assert(!thread_.joinable() && "Derivative classes MUST call stop() from their destructors");
    }

    void start()
    {
        stop();
        io_service_.reset();
        work_.reset(new boost::asio::io_service::work(io_service_));
        std::thread(std::bind(&BaseActor::threadFunc_, this)).swap(thread_);
    }

    void stop()
    {
        work_.reset();
        io_service_.stop();
        if (thread_.joinable())
            thread_.join();
    }

protected:
    BaseActor()
        : thread_()
    {
    }
    virtual void threadFunc_() { io_service_.run(); }

protected:
    template <class T, class... Args>
    std::function<void(Args...)> makeSlotProxy_(void (T::*f)(Args...))
    {
        return io_service_.wrap([this, f](Args&&... args) { (static_cast<T*>(this)->*f)(std::forward<Args>(args)...); });
    }

    template <typename F>
    class FunctorTraits_;
    template <class R, class T, class... Args>
    class FunctorTraits_<R (T::*)(Args...)> {
    public:
        typedef std::function<R(Args...)> FunctorType;
    };

    template <typename F, F>
    class SlotProxyFactory_;
    template <typename T, typename... Args, void (T::*F)(Args...)>
    class SlotProxyFactory_<void (T::*)(Args...), F> {
    public:
        SlotProxyFactory_(T* c)
            : t_(c)
        {
        }

        std::function<void(Args...)> operator()()
        {
            return t_->io_service_.wrap([&](Args&&... args) { (t_->*F)(std::forward<Args>(args)...); });
        }

    private:
        T* t_ = nullptr;
    };

protected:
    std::thread thread_;
    std::unique_ptr<boost::asio::io_service::work> work_;
    boost::asio::io_service io_service_;
};

class ActorA : public BaseActor {
public:
    ~ActorA() { stop(); }

public:
    boost::signals2::signal<void(const std::string&)> helloSig;
    std::atomic_int aNumber{ 0 };

protected:
    virtual void threadFunc_() override
    {
        print("ActorA has started", "\n");
        BaseActor::threadFunc_();
        print("ActorA has finished", "\n");
    }

private:
    void doJobA_(const int& num)
    {
        print("ActorA::doJobA_() from thread ", thread_.get_id(), "\n");
        print("num = ", num, "\n");
        std::ostringstream oss;
        oss << "Hello, World " << num << "!";
        helloSig(oss.str());
        aNumber.store(num);
    }

public:
    FunctorTraits_<decltype(&ActorA::doJobA_)>::FunctorType jobASlot()
    {
        return makeSlotProxy_(&ActorA::doJobA_);
    }
};

class ActorB : public BaseActor {
public:
    ~ActorB() { stop(); }

public:
    boost::signals2::signal<void(const int&)> intSig;

protected:
    virtual void threadFunc_() override
    {
        print("ActorB has started", "\n");
        BaseActor::threadFunc_();
        print("ActorB has finished", "\n");
    }

private:
    void doJobB_(const std::string& str)
    {
        print("ActorB::doJobB_() from thread ", thread_.get_id(), "\n");
        print("str = ", str, "\n");
        intSig(++count_);
        aString_ = str;
    }

public:
    FunctorTraits_<decltype(&ActorB::doJobB_)>::FunctorType getJobBSlot()
    {
        return makeSlotProxy_(&ActorB::doJobB_);
    }

    SlotProxyFactory_<decltype(&ActorB::doJobB_), &ActorB::doJobB_> jobBSlot{this};

private:
    int count_ = 0;
    std::string aString_;
};

int main()
{
    print("Main thread id is ", std::this_thread::get_id(), "\n");

    ActorA actorA;
    ActorB actorB;
    actorA.helloSig.connect(actorB.jobBSlot());
    actorB.intSig.connect(actorA.jobASlot());
    actorA.start();
    actorB.start();
    actorB.intSig(0);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

Попробовать можно здесь: http://rextester.com/HJM94541 Теперь нужно придумать как заставить это как-то работать в MSVS 2013... видимо, без всяких фабрик.

asaw ★★★★★
() автор топика
Ответ на: комментарий от asaw

Теперь нужно придумать как заставить это как-то работать в MSVS 2013... видимо, без всяких фабрик.

В таком виде получилось: http://rextester.com/GPZI19349

asaw ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.