【问题标题】:Boost::signals2 passing invalid dataBoost::signals2 传递无效数据
【发布时间】:2014-10-13 18:18:51
【问题描述】:

我从事 C/C++ 开发人员已有大约 20 年了,但模板一直是我的弱点。随着模板编程在 C++11 和 C++14 标准中变得越来越有用和复杂,我决定尝试一个练习来学习。我已经取得了一定的成功,但我遇到了一个问题。我有以下课程:

namespace Events {
// Place your new EventManager events here
static const uint32_t StatsData = 0;
static const uint32_t StatsRequest = 1;
static const uint32_t StatsReply = 2;
static const uint32_t ApplianceStatsRequest = 3;
static const uint32_t ApplianceStatsReply = 4;
static const uint32_t NullEvent = 5;
};

class EventManager {
    public:
    static EventManager *instance() {
        if (Instance)
            return Instance;

        return new EventManager();
    };

    static void destroy() {
        delete Instance;
        Instance = nullptr;
    }

    template<typename T>
    bool consume_event(uint32_t event, std::function<T> func) {
        if (_event_map.find(event) == _event_map.end())
            // Create the signal, in true RAII style
            _event_map[event] = new boost::signals2::signal<T>();

        boost::any_cast<boost::signals2::signal<T> *>(_event_map[event])->connect(func);

        return true;
    }

    void emit(uint32_t event) {
        if (_event_map.find(event) == _event_map.end())
            return;

        try {
            boost::signals2::signal<void()> *sig =
                boost::any_cast<boost::signals2::signal<void()> *>(_event_map[event]);

                (*sig)();
        }
        catch (boost::bad_any_cast &e) {
            SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
            abort();
        }
    }

    template<typename... Args>
    void emit(uint32_t event, Args... args) {
        if (_event_map.find(event) == _event_map.end())
            return;

        try {
            boost::signals2::signal<void(Args...)> *sig =
                boost::any_cast<boost::signals2::signal<void(Args...)> *>(_event_map[event]);
            (*sig)(args...);
        }
        catch (boost::bad_any_cast &e) {
            SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
            abort();
        }
    }

private:
    EventManager() { Instance = this; }; 
    ~EventManager() { Instance = nullptr; };

    static EventManager *Instance;
    std::map<uint32_t, boost::any> _event_map;
};

此代码可能会进入一个大型框架,该框架会加载多个模块,这些模块是 linux 上的动态库。这个想法是让给定的模块能够调用:

consume_event<ParamTypes><EventNumber, SomeCallack)

回调可能是带有签名 void(ParamTypes) 的函数,或者是 std::bind() 对带有签名 void(ParamTypes) 的函数的结果。

然后另一个模块可以调用:

emit<ParamTypes>(EventNumber, ParamValues) 

每个调用了 consume_event 的模块都会使用 ParamValues 调用它的处理程序。

这似乎在几乎所有情况下都有效,除非我将引用传递给自定义类,如下所示:

std::cout << "Sending stats data with ref: " << std::hex << ip_entry.second <<  std::endl;
emit<ip_stats_t &>(Events::StatsData, *ip_entry.second);

在这种情况下,连接到信号的函数接收到 0xa,并在尝试将其视为 ip_stats_t & 时立即崩溃。

输出是:

Sending stats data with ref: 0x7fbbc4177d50 <- This is the output of the line seen above
ips addr: 0xa << this is from the function that gets called by the signal.

更新:我刚刚注意到它在通过引用传递任何变量时都会做同样的事情,而不仅仅是上面的自定义类。

另外,请注意,此问题中没有SSCCE,因为任何 SSCCE 不变式都有效。在将工作代码放入上述框架之前,不会出现问题。

Update2:这里真正的问题是,如何才能使这种设计变得更好。这不仅不能正常工作,而且在语法上,它很臭。它丑陋、不雅,而且真的没有什么好处,只是它做了我想要它做的事情并增加了我对模板的理解。

Update3:我现在 100% 确认这与我传递的数据类型无关。如果我通过引用传递 any 变量,则插槽始终接收 0xa 作为引用的地址。这包括 std::strings,甚至 int。如果我按值传递任何变量,则该值的复制构造函数最终会接收 0xa 作为要从中复制的值的引用。这只发生在从模块 A 中创建的信号调用模块 B 中的插槽时。我错过了什么?

有什么想法吗? 谢谢!

【问题讨论】:

  • 未显示:检查 ip_entry.second 是否不是过时的指针(例如,指向超出范围的堆栈对象或已删除的堆对象)
  • 谢谢,它肯定不是一个过时的指针。这些是存储在映射中的指针,在包含它的整个类被删除之前不会被删除。为了确保,我通过 GDB 检查了数据,它是有效的。
  • 很高兴把它排除在外!
  • 在您添加的代码中,您仍然没有显示相关的移动部件。对于失败的案例,您如何调用 consume_event&lt;&gt;emit&lt;&gt;?请为此制作一个SSCCE。你很快就会发现问题。 (另外,您是否跨动态模块边界调用?)
  • 感谢您的关注,但我认为问题stackoverflow.com/questions/7957239/… 对我正在尝试做的事情有一些有趣的可能性。它似乎使用单个 void() 信号并使用 std::bind() 传递参数来实现您之前建议的内容。我要试一试。感谢您的意见!

标签: c++ c++11 boost signals2


【解决方案1】:

更新我已经提出了一个看起来更接近你想要达到的目标的演示:

@lk75 有趣的是,这里有一种以相当可扩展的方式抽象事件机制的方法,而

  • 不要过于复杂
  • 不需要到处重复调用签名(现在在Traits
  • 使用真正的 RAII 样式 (SCNR) 不会泄漏信号。不再使用newdelete

查看 Live On Coliru

请注意我是如何简化单例并将 consume_eventemit 现在变成单行的:

    static EventManager& instance() {
        static EventManager instance;
        return instance;
    };

    template <EventId event, typename F>
    bool consume_event(F&& func) {
        get_slot<event>().connect(std::forward<F>(func));
        return true;
    }

    template <EventId event, typename... Args>
    void emit(Args&&... args) {
        get_slot<event>()(std::forward<Args>(args)...);
    }

完整代码

供参考:

Live On Coliru

#include <boost/any.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <memory>
#include <string>

struct ip_stats_t {
    std::string canary;
};

enum class EventId : uint32_t {
    // Place your new EventManager events here
    StatsData             = 0,
    StatsRequest          = 1,
    StatsReply            = 2,
    ApplianceStatsRequest = 3,
    ApplianceStatsReply   = 4,
    NullEvent             = 5, // Not implemented
};

namespace Events {

    template <EventId> struct Traits;

    template <> struct Traits<EventId::StatsData>             { using signal_type = boost::signals2::signal<void(int)>;                 } ;
    template <> struct Traits<EventId::StatsRequest>          { using signal_type = boost::signals2::signal<void(bool, bool)>;          } ;
    template <> struct Traits<EventId::StatsReply>            { using signal_type = boost::signals2::signal<void(std::string)>;         } ;
    template <> struct Traits<EventId::ApplianceStatsRequest> { using signal_type = boost::signals2::signal<void(double, ip_stats_t&)>; } ;
  //template <> struct Traits<EventId::NullEvent>             { using signal_type = boost::signals2::signal<void()>;                    } ;

    template <> struct Traits<EventId::ApplianceStatsReply> : Traits<EventId::ApplianceStatsRequest> { }; 
}

class EventManager {
  public:
    static EventManager& instance() {
        static EventManager instance;
        return instance;
    };

    template <EventId event, typename F>
    bool consume_event(F&& func) {
        get_slot<event>().connect(std::forward<F>(func));
        return true;
    }

    template <EventId event, typename... Args>
    void emit(Args&&... args) {
        get_slot<event>()(std::forward<Args>(args)...);
    }

  private:
    template <EventId event, typename Slot = typename Events::Traits<event>::signal_type, typename SlotPtr = boost::shared_ptr<Slot> >
    Slot& get_slot() {
        try {
            if (_event_map.find(event) == _event_map.end())
                _event_map.emplace(event, boost::make_shared<Slot>());

            return *boost::any_cast<SlotPtr>(_event_map[event]);
        }
        catch (boost::bad_any_cast const &e) {
            std::cerr << "Caught instance of boost::bad_any_cast: " << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
            abort();
        }
    }

    EventManager() = default;
    std::map<EventId, boost::any> _event_map;
};

int main() {
    auto& emgr = EventManager::instance();

    emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) { 
            std::cout << "d: " << d << ", v.canary: " << v.canary << "\n";
        });
    emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) { 
            std::cout << "And you can register more than one\n";
        });


    ip_stats_t v { "This is statically checked" };
    emgr.emit<EventId::ApplianceStatsRequest>(3.142f, v);

    emgr.emit<EventId::StatsData>(42); // no connection, but works
    emgr.consume_event<EventId::StatsData>([](int) { std::cout << "Now it's connected\n"; });
    emgr.emit<EventId::StatsData>(42); // now with connection!

#if 0
    emgr.emit<EventId::ApplianceStatsRequest>();  // error: no match for call to ‘(boost::signals2::signal<void(double, ip_stats_t&)>) ()’
    emgr.consume_event<EventId::NullEvent>([]{}); // use of incomplete type Traits<NullEvent>
#endif
}

旧答案:

您似乎在使用可变参数转发时遇到了问题:

    (*sig)(std::forward<Args>(args)...);

此外,只有在通过“通用引用”获取参数时,转发才真正有意义:

template<typename... Args>
void emit(uint32_t event, Args&&... args) { // NOTE!!

但是,您不依赖参数类型推导来获得实际值类别(右值与左值)。而且,确实如此(因为编译器可能永远不会获得“正确”的确切参数类型以匹配存储的信号(使 any_cast 充其量会失败,或者调用Undefined Behaviour 充其量。)

所以在这种情况下,你应该放弃整个转发业务:

template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;

template<typename... Args>
void emit(uint32_t event, Args... args) {
    if (_event_map.find(event) == _event_map.end())
        return;

    try {
        Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);

        (*sig)(args...);
    }
    catch (boost::bad_any_cast &e) {
        std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
        abort();
    }
}

完整的演示程序:Live On Coliru

#include <boost/any.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <string>

struct ip_stats_t { 
    std::string canary;
};

template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;
std::map<uint32_t, boost::any> _event_map;

template<typename... Args>
void emit(uint32_t event, Args&&... args) {
    if (_event_map.find(event) == _event_map.end())
        return;

    try {
        Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);

        (*sig)(std::forward<Args>(args)...);
    }
    catch (boost::bad_any_cast &e) {
        std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
        abort();
    }
}

int main()
{
    Sig<int, double> sig1;
    Sig<ip_stats_t&> sig2;

    sig1.connect([](int i, double d) { std::cout << "Sig1 handler: i = " << i << ", d = " << d << "\n"; });
    sig2.connect([](ip_stats_t& v)   { std::cout << "Sig2 handler: canary = " << v.canary << "\n"; });

    _event_map[1] = &sig1;
    _event_map[2] = &sig2;

    emit<int, double>(1, 42, 3.14);

    ip_stats_t instance { "Hello world" }, *ptr = &instance;

    emit<ip_stats_t&>(2, *ptr);
}

【讨论】:

  • 感谢您的出色回答。 std::forward 实际上是我在尝试使用通用引用的代码时忘记删除的保留。不幸的是,删除 std::forward 没有效果。我还是有同样的问题
  • @lk75 你运行我的代码了吗?看看有什么不同。也许这会帮助您制作您未能在问题中包含的 SSCCE(请参阅Solve your problem by almost asking a question on StackOverflow
  • 哦,我忘了说:看起来你真的把事情复杂化了。您可能应该只使用一个修复签名signal&lt;void()&gt;,然后将 绑定处理程序 连接到该签名(使用boost::bind 或 c++11 lambda 表达式)。然后你可以简单地删除any+any_cast
  • 不错的干净实现。我非常喜欢它,它教会了我更多关于模板编程的知识。不幸的是,出于完全相同的原因,它仍然在完全相同的位置崩溃。在这一点上,我已经尝试了 5 种不同的实现,你的 2 种,我自己的 3 种,但没有任何效果。它必须与模块之间传递数据有关,尽管我不明白按值传递数据会出现什么问题,这也会以同样的方式崩溃。再次感谢您的宝贵时间!
  • @lk75 在这一点上,我会说你有一个 ODR 违规/ABI 不兼容(这意味着你的动态库可能对 ip_stat_t 的外观有不同的想法,例如)。编译器标志、编译器版本、预处理器指令等可以巧妙地改变类型的含义和布局(想想对齐、填充、整数大小等)。希望你能找到。如果你不能隔离罪魁祸首,也许你可以做一个最小的破坏样本。
【解决方案2】:

以下代码是 Sehe 修改后的代码,没有 boost::signals 完全解决了我的问题。 boost::signals 似乎在跨模块边界传递任何数据时遇到问题。用简单的函数向量替换它在所有情况下都有效,而且速度更快!

enum class EventId : uint32_t {
    // Place your new EventManager events here
    StatsData             = 0,
    StatsRequest          = 1,
    StatsReply            = 2,
    ApplianceStatsRequest = 3,
    ApplianceStatsReply   = 4,
};

struct ip_stats_t;

namespace Events {
    template <EventId> struct Traits;

    template <> struct Traits<EventId::StatsData>             
        { using signal_vec = std::vector<std::function<void(ip_stats_t &)>>; } ;

    template <> struct Traits<EventId::StatsRequest>          
        { using signal_vec = std::vector<std::function<void(std::ostream &)>>; } ;

    template <> struct Traits<EventId::StatsReply>            
        { using signal_vec = std::vector<std::function<void(std::string &)>>; } ;

    template <> struct Traits<EventId::ApplianceStatsRequest> :
        Traits<EventId::StatsRequest> {};

    template <> struct Traits<EventId::ApplianceStatsReply> : 
        Traits<EventId::StatsReply> {}; 
}

class EventManager {
    public:
        static EventManager& instance() {
            static EventManager instance;
            return instance;
        };

        template <EventId event, typename F>
        void consume_event(F&& func) {
            get_slot<event>().push_back(std::forward<F>(func));
        }

        template <EventId event, typename... Args>
        void emit(Args&&... args) {
            for (auto &vi : get_slot<event>()) {
                vi(std::forward<Args>(args)...);
            }
        }

    private:
        template <EventId event, 
            typename Slot = typename Events::Traits<event>::signal_vec,
            typename SlotPtr = std::shared_ptr<Slot>>
        Slot& get_slot() {
            if (_event_map.find(event) == _event_map.end())
                _event_map.emplace(event, std::make_shared<Slot>());

            try {
                return *boost::any_cast<SlotPtr>(_event_map[event]);
            }
            catch (boost::bad_any_cast const &e) {
                std::cerr << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
                abort();
            }
        }

    EventManager() = default;
    std::map<EventId, boost::any> _event_map;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-11
    • 2014-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多