boost的signal和solt机制使用入门

本文根据boost的教程文档整理。

signal-slot是一个非常方便的接口机制,在Qt和Gtk中广泛使用。boost也实现了一个signal-slot机制。


编译包含signal-slot的代码

使用signal-slot,必须包含头文件

#include <boost/signal.hpp>

signal-slot在boost中不是纯头文件,需要一个libboost_signals.so文件,在编译时,需要

g++ -o signal2 signal2.cpp -l boost_signals


初见signal-slot

从HelloWorld开始吧

首先定义hellword函数

void helloworld() {
    std::cout << "Hello, World!(func)" << std::endl;
}

然后,定义signal对象

boost::signal<void ()>sig;

在main函数中使用

int main()
{
    sig.connect(&helloworld);
    sig();
}
sig()相当与emit。


除了直接的对象外,还可以使用函数对象

struct HelloWorld {
    void operator() () const
    {
        std::cout << "Hello, World!" << std::endl;
    }
};

在main函数中,这样使用

    HelloWorld hello;
    sig.connect(hello);

还可以使用bind,(请#include <boost/bind.hpp>)

void printMore(const std::string& user)
{
    std::cout << user << " say: Hello World!\n";
}
在main函数中,这样使用

    sig.connect(boost::bind(printMore, "Tom"));
    sig.connect(boost::bind(printMore, "Jerry"));
打印的结果是

Tom say: Hello World!
Jerry say: Hello World!

singal-slot的顺序

默认情况下,signal-slot是按照添加顺序进行的,例如

struct Hello {
    void operator() () const
    {
        std::cout << "Hello ";
    }
};
struct World {
    void operator() () const
    {
        std::cout << ", World" << std::endl;
    }
};

如果这样写

    sig.connect(Hello());
    sig.connect(World());
输入的结果是

Hello , World
先调用了Hello,后调用了World

但是,如果这样写

    sig.connect(1, World());
    sig.connect(0, Hello());

结果仍然同上面的一样。

signal connection的管理

disconnection

signal disconnect方法

sig.connect(&helloworld);
....
sig.connect(&helloworld);

目前发现的只有函数可以这样做,函数对象,bind对象都不可以。


connection对象的disconnect方法

    HelloWorld hello;
    boost::signals::connection c = sig.connect(hello);
....

    c.disconnect();

block slot

slot可以被暂时阻止,然后在恢复,如

    HelloWorld hello;
    boost::signals::connection c = sig.connect(hello);
.....
    c.block();
    sig();
....
    c.unblock();
    sig();

block和unblock都是boost::signals::connection对象的方法,需要首先得到这个connection。

在作用域范围内的slot

{
  boost::signals::scoped_connection c = sig.connect(ShortLived());
  sig(); // will call ShortLived function object
}
sig(); // ShortLived function object no longer connected to sig

ShortLive只在作用域内起作用,如果离开了作用域,就不能起作用了。

slot的自动跟踪

考虑下面的代码

boost::signal<void (const std::string&)> deliverMsg;
void autoconnect()
{
    MessageArea * msgarea = new MessageArea();

    deliverMsg.connect(boost::bind(&MessageArea::displayMessage, msgarea, _1));

    deliverMsg("hello world!");
        
    delete msgarea;
    //Oops, msgarea is deleted!
    deliverMsg("again!");

}
最后一个deliverMsg被调用时,msgarea已经被删除了,通常情况下,这会引起崩溃。为了避免这个问题,boost引入一个trackable对象。

请看MessageArea的声明

struct MessageArea : public boost::signals::trackable
{
public:
    void displayMessage(const std::string& msg) {
        std::cout<<"** the message is: " << msg<<std::endl;
    }
};
派生自 boost::signals::trackable,就可以解决这个自动关闭的问题了!

autoconnect函数只会调用一次displayMessage。在delete msgarea发生后,deliverMsg对应的slot就被删除了。

带参数和返回值的signal slot


带参数的signal

signal可以添加任意多参数的,比如这个例子

void print_sum(float x, float y)
{
    std::cout << "The sum is " << x + y << std::endl;
}

void print_product(float x, float y)
{
    std::cout << "The product is " << x * y << std::endl;
}


void print_difference(float x, float y)
{
    std::cout << "The difference is " << x * y << std::endl;
}

定义和使用signal

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

    sig.connect(&print_sum);
    sig.connect(&print_product);
    sig.connect(&print_difference);

    sig(5, 3);
}
我们得到的结果,将是

The sum is 8
The product is 15
The difference is 15

带返回值的slot

float product(float x, float y) { return x*y; }
float quotient(float x, float y) { return x/y; }
float sum(float x, float y) { return x+y; }
float difference(float x, float y) { return x-y; }


int main(void)
{
    boost::signal<float (float x, float y)> sig;

    sig.connect(&product);
    sig.connect("ient);
    sig.connect(&sum);
    sig.connect(&difference);

    std::cout << sig(5, 3) << std::endl;
}

最后的结果是" 2",这是最后一个slot difference的结果。signal默认返回最后一个slot的值。


增加返回值处理器

如果这不是你想要的值,你可以增加新的返回值处理器来实现

template<typename T>
struct maximum
{
    typedef T result_type;
    template<typename InputIterator>
    T operator()(InputIterator first, InputIterator last) const
    {
        if(first == last)
            return T();

        T max_value = *first ++;
        while(first != last) {
            if(max_value < *first)
                max_value = *first;
            ++first;
        }
        return max_value;
    }
};

maximum是一个函数对象,它必须接收两个参数 InputIterator first和last,返回T类型对象。这个例子中,它获取返回值中的最大值。


它是这样使用的

    boost::signal<float (float x, float y), maximum<float> > sig;
......
.....

在 signal声明时,作为模板参数给出。


我们还可以收集slot的返回值,这通过定义一个收集器实现

template<typename Container>
struct aggregate_values
{
    typedef Container result_type;

    template<typename InputIterator>
    Container operator()(InputIterator first, InputIterator last) const
    {
        return Container(first, last);
    }
};

这样使用

    boost::signal<float (float x, float y), aggregate_values<std::vector<float> > > sig2;

    sig2.connect("ient);
    sig2.connect(&product);
    sig2.connect(&sum);
    sig2.connect(&difference);

    std::vector<float> results = sig2(5,3);
    std::copy(results.begin(), results.end(),
            std::ostream_iterator<float>(std::cout, " "));
    std::cout<<std::endl;

slot执行的结果,将被放在vector<float>对象中,并可以被访问。


这个返回值收集器在工作的时候,如果first和last没有被访问到,那么,slot就不会被触发。例如

template<typename T>
struct FirstResult
{
   template<class InputIterator>
   T operator()(InputIterator first, InputIterator last) {
        return *first;
   }
};

这个收集器事实上,仅仅让signal触发了第一个slot,其余的slot均没有被触发。因此,这个slot也可以作为我们过滤slot的方法。


slot_type传递slot

slot和signal的声明不会在一个地方(如果那样,就没有必要提供signal-slot机制了),这是,我们需要传递 slot对象,具体做法,是通过signal::slot_type来完成的

如,下面的例子:

class Button
{
    typedef boost::signal<void (int x, int y)> OnClick;

public:
    void addOnClick(const OnClick::slot_type& slot);

    void press(int x, int y) {
        onClick(x, y);
    }

private:
    OnClick onClick;
};

void Button::addOnClick(const OnClick::slot_type& slot)
{
   onClick.connect(slot);
}
OnClick::slot_type定义了slot的类型,且可下面的使用
void printCoordinates(long x, long y)
{
    std::cout<<"Button Clicked @(" << x << "," << y <<")\n";
}

void button_click_test()
{
    Button button;

    button.addOnClick(&printCoordinates);

    std::cout<<"===== button onclick test\n";

    button.press(200,300);
    button.press(20,30);
    button.press(19,3);
}
button.addOnClick可以直接接收任何能够被signal.connect接受的参数。

来个综合的例子:Document-View

定义Document类

class Document
{
public:
    typedef boost::signal<void (bool)> signal_t;
    typedef boost::signals::connection connect_t;

public:
    Document(){ }
    connect_t connect(signal_t::slot_function_type subscriber)
    {
        return m_sig.connect(subscriber);
    }

    void disconnect(connect_t subscriber)
    {
        subscriber.disconnect();
    }

    void append(const char* s)
    {
        m_text += s;
        m_sig(true);
    }

    const std::string& getText() const { return m_text; }

private:
    signal_t m_sig;
    std::string m_text;
};

注意到m_sig定义了一个信号对象。

View对象建立起和Document的联系

class View
{
public:
    View(Document& m)
        :m_doc(m)
    {
        m_conn = m_doc.connect(boost::bind(&View::refresh, this, _1));
    }

    virtual ~View()
    {
        m_doc.disconnect(m_conn);
    }

    virtual void refresh(bool bExtended) const = 0;

protected:
    Document& m_doc;
private:
    Document::connect_t m_conn;
};

两个派生类TextView和HexView

class TextView : public View
{
public:
    TextView(Document& doc) : View(doc) { }

    virtual void refresh(bool bExtended) const {
        std::cout << "TextView:" << m_doc.getText() << std::endl;
    }
};

class HexView : public View
{
public:
    HexView(Document& doc) : View(doc) { }

    virtual void refresh(bool bExtended) const {
        std::cout << "HexView: ";
        const std::string& s = m_doc.getText();

        for(std::string::const_iterator it = s.begin();
            it != s.end(); ++it)
            std::cout << ' ' << std::hex << static_cast<int>(*it);
        std::cout << std::endl;
    }
};

使用方法:

void document_view_test()
{
    Document doc;
    TextView v1(doc);
    HexView  v2(doc);

    std::cout<<"================= document view test ===============\n";

    doc.append("Hello world!\n");
    doc.append("Good!\n");
    doc.append("Happy!\n");
}

该代码运行后,可以看到如下的结果

================= document view test ===============
TextView:Hello world!

HexView:  48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a
TextView:Hello world!
Good!

HexView:  48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a 47 6f 6f 64 21 a
TextView:Hello world!
Good!
Happy!

HexView:  48 65 6c 6c 6f 20 77 6f 72 6c 64 21 a 47 6f 6f 64 21 a 48 61 70 70 79 21 a





  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值