boost -- signals 教程翻译

教程

如何阅读教程
兼容性提示
Hello, World! (初级)
使用多个槽
向槽/从槽中 传递数值
管理连接的槽
例子: Document-View 链接库

如何阅读教程

没有必要一行一行的阅读这个教程. 上面的结构大致分离了库的几个不同的主题( 比如处理多个槽, 向槽中/ 从槽往外传递数据 ) 并且在每一个主题中基本概念都是首次被提到接着是复杂点的应用.每一节都使用初级 中级 高级 标志来指导读者,初级包含了所有的使用者都需要知道的信息, 只要你读完了 初级 章节你就能很好的应用Signals库.中级 建立在初级的基础上,添加了一点点复杂的使用. 最后,高级章节告诉非常高深的使用者库的详细信息, 这通常需要非常坚固的初级中级章节的知识,大多数使用者不需要阅读高级章节.


兼容性提示

Boost.Signals 有两种语法格式: 推荐的格式和兼容的格式. 推荐的格式更接近与C++语言并且减少了需要考虑的模板参数且可读性好. 然而推荐的格式由于一些编译问题并不是

支持所有的平台.兼容格式将会在所有支持boost signals的编译器上通过编译。参考下面的表格来针对你的编译器决定使用哪种格式。使用boost function的使用者,请注意推荐signal格式和推荐的function格式相同。

如果你的编译器没有出现在下面的列表中,请尽量使用推荐格式并且将结果汇报给boost list, 这样我们就能及时更新。


推荐语法移植性语法
  • GNU C++ 2.95.x, 3.0.x, 3.1.x

  • Comeau C++ 4.2.45.2

  • SGI MIPSpro 7.3.0

  • Intel C++ 5.0, 6.0

  • Compaq's cxx 6.2

  • Microsoft Visual C++ 7.1

  • Any compiler supporting the preferred syntax

  • Microsoft Visual C++ 6.0, 7.0

  • Borland C++ 5.5.1

  • Sun WorkShop 6 update 2 C++ 5.3

  • Metrowerks CodeWarrior 8.1

Hello, World! (初级)

下面的例子使用信号和槽机制写 "Hello, World!" 。首先,我们创建信号sig,它不接受参数也没有返回值。接着我们使用connect接口链接到hello。最后我们像函数一样使用信号sig,也就是调用了HelloWorld::operator() 来打印 "Hello,World!".

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

// ...

// Signal with no arguments and a void return value
boost::signal<void ()> sig;

// Connect a HelloWorld slot
HelloWorld hello;
sig.connect(hello);

// Call all of the slots
sig();
struct HelloWorld 
{
  void operator()() const 
  { 
    std::cout << "Hello, World!" << std::endl;
  } 
};

// ...

// Signal with no arguments and a void return value
boost::signal0<void> sig;

// Connect a HelloWorld slot
HelloWorld hello;
sig.connect(hello);

// Call all of the slots
sig();

调用多个槽

连接多个槽 (初级)

使用一个信号来调用一个槽没大有意思,我们通过在两个分离的槽中各自打印Hello和World来让Hello World这个例子更有趣。第一个槽打印Hello,基本上是下面这样:

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

第二个槽打印World和换行:

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

像我们上一个例子一样,我们可以创建信号sig。 不需要参数也没有返回值。 这一次,我们将Hello和World俩槽连接到一个信号上。然后我们调用信号俩槽就都被调用了。

Preferred syntaxPortable syntax
boost::signal<void ()> sig;

sig.connect(Hello());
sig.connect(World());

sig();
boost::signal0<void> sig;

sig.connect(Hello());
sig.connect(World());

sig();

槽的默认调用顺序是FIFO,所以打印:

Hello, World!
为槽组的调用排序(中级)
槽可以随意有一些边际效力,这也就意味着有些槽应该在其他的槽之前调用,虽然说他们连接的顺序不是这样的。Boost.Signals 库允许槽组通过某些方式在排序。在我们的Hello World程序中,我们希望Hello先于World打印。所以我们将Hello放到一个必须在World组之前执行的组中。为了实现这个,我们可以通过为初级的connect接口添加额外的参数来指定组。默认的组参数是int 。数字越小越靠前。这是我们构造Hello World的方式:


Preferred syntaxPortable syntax
boost::signal<void ()> sig;
sig.connect(1, World());
sig.connect(0, Hello());
sig();
boost::signal0<void> sig;
sig.connect(1, World());
sig.connect(0, Hello());
sig();

程序顺利的打印Hello World。 这是因为Hello在组0中,比World的组1要靠前。组的参数是可选的。我们在第一个例子中缺省它是因为如果各个槽都是独立的话,他就是非必要的。如果我们混合调用带有组参数的和不带组参数的话,不带组参数的就可以被放置到组的组列表的前/后面(通过传递boost::signals::at_front orboost::signals::at_back做为connect的最后一个参数),默认是在后面。如果我们在添加一个槽的话:

struct GoodMorning
{
  void operator()() const
  {
    std::cout << "... and good morning!" << std::endl;
  }
};

sig.connect(GoodMorning());

... 我们就会得到::

Hello, World!
... and good morning!

向槽/从槽中传递数值


槽参数 (初级)
信号可以向每个槽传递参数。比如,一个传播鼠标时间的信号可能希望传递新的鼠标坐标和当前被点击的按键。

作为一个例子,我们创建一个给他的槽传递2个float参数的信号。然后我们创建一些槽来用这些数据做各种数学运算:

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

void print_quotient(float x, float y)
{
  std::cout << "The quotient is " << x/y << std::endl;
}
Preferred syntaxPortable syntax
boost::signal<void (float, float)> sig;

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

sig(5, 3);
boost::signal2<void, float, float> sig;

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

sig(5, 3);

这个程序打印::

The sum is 8
The product is 15
The difference is 2
The quotient is 1.66667

因此当信号被调用时候,各种数值都可能被sig传递给它的各个槽。我们必须在我们定义信号的时候提前声明这个数据类型。

boost::signals<void (float ,float)> 表示这个信号没有返回值,并且接受两个float参数。任何一个通过connect连接到这个信号的槽都必须能得接受两个float参数。


信号的返回值 (高级)
就好像信号可以接受参数似的,信号也可以返回数值。这些数值可以通过一个信号调用者的组合者来返回。这个组合者是一个得到被调用的槽的结果(可能没有返回值可以能有100个,不到程序运行是不知道的)并且聚合他们返回给调用者。信号的结果可能是一个槽调用结果的一个简单函数,最后一个槽的结果,返回值的最大/最小值。。。。

我们可以简单的修改之前的数学运算让所有的槽返回他们的计算结果。然后信号自己基于这些结果来返回值:


Preferred syntaxPortable syntax
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; }

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

sig.connect(&product);
sig.connect(&quotient);
sig.connect(&sum);
sig.connect(&difference);

std::cout << sig(5, 3) << std::endl;
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; }

boost::signal2<float, float, float> sig;

sig.connect(&product);
sig.connect(&quotient);
sig.connect(&sum);
sig.connect(&difference);

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

这个例子输出2. 因为信号默认调用所有的槽然后返回最后一个调用的槽的返回值。对这个例子来说,这个返回值有些弱智,因为这些槽没有相互作用。

一个更有趣的例子是信号的结果会是所有的槽的返回值的最大值。为了实现这个,我们可以创建一个客户的组合者:

template<typename T>
struct maximum
{
  typedef T result_type;

  template<typename InputIterator>
  T operator()(InputIterator first, InputIterator last) const
  {
    // If there are no slots to call, just return the
    // default-constructed value
    if (first == last)
      return T();

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

这个maximum 模板类像仿函数一个工作。它从一串float(加入是maximum <float> )中找到最大的。当一个maximum 执行的时候,它的给定序列[first , last)包含了所有的槽的执行结果。返回的是最大值。

我们通过将他作为信号的组合者来使用它。这个组合者参数遵循信号的调用签名:

Preferred syntaxPortable syntax
boost::signal<float (float x, float y), 
              maximum<float> > sig;
boost::signal2<float, float, float, 
               maximum<float> > sig;

现在我们可以连接这个计算函数并且使用信号了:

sig.connect(&quotient);
sig.connect(&product);
sig.connect(&sum);
sig.connect(&difference);

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

输出是15.

或许我们希望返回所有的计算结果。在一个更大的数据结构中。可以简单的通过构造一个不一样的组合者来实现:

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

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

然后我们又可以连接这个计算函数并且使用信号了:

Preferred syntaxPortable syntax
boost::signal<float (float, float), 
    aggregate_values<std::vector<float> > > sig;

sig.connect(&quotient);
sig.connect(&product);
sig.connect(&sum);
sig.connect(&difference);

std::vector<float> results = sig(5, 3);
std::copy(results.begin(), results.end(), 
    std::ostream_iterator<float>(cout, " "));
boost::signal2<float, float, float,
    aggregate_values<std::vector<float> > > sig;

sig.connect(&quotient);
sig.connect(&product);
sig.connect(&sum);
sig.connect(&difference);

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

这个程序的输出将包括 15, 8, 1.6667 和 2. 有趣的是signal模板的第一个参数float 并不是这个信号的实际返回值. float真正对应的是连接的槽的返回值以及传递给组合者value_type. 组合者是一个仿函数并且它的result_type成为信号的返回类型.


The input iterators passed to the combiner transform dereferenceoperations into slot calls. Combiners therefore have the option toinvoke only some slots until some particular criterion is met. Forinstance, in a distributed computing system, the combiner may askeach remote system whether it will handle the request. Only oneremote system needs to handle a particular request, so after aremote system accepts the work we do not want to ask any otherremote systems to perform the same task. Such a combiner need onlycheck the value returned when dereferencing the iterator, andreturn when the value is acceptable. The following combiner returnsthe first non-NULL pointer to aFulfilledRequest datastructure, without asking any later slots to fulfill therequest:

struct DistributeRequest {
  typedef FulfilledRequest* result_type;

  template<typename InputIterator>
  result_type operator()(InputIterator first, InputIterator last) const
  {
    while (first != last) {
      if (result_type fulfilled = *first)
        return fulfilled;
      ++first;
    }
    return 0;
  }
};

链接管理

取消链接 (Beginner)

槽并不是打算链接后就永远存在. 槽通常是在触发了某些事件后链接并且之后就取消了, 程序员需要掌控一个槽什么时候不再应该链接到信号上.

显示标志一个链接的实体是boost::signals::connection类.connection类唯一的标识一个信号和槽的链接.connected()接口可以检测信号和槽是否还链接着 .如果信号和槽以及链接, 那么就使用disconnect()接口用来取消这个链接. 每一次调用connect()接口都返回一个connection对象, 可以用来检测他们是否仍然链接或者取消链接.


boost::signals::connection c = sig.connect(HelloWorld());
if (c.connected()) {
// c is still connected to the signal
  sig(); // Prints "Hello, World!"
}

c.disconnect(); // Disconnect the HelloWorld object
assert(!c.connected()); c isn't connected any more

sig(); // Does nothing: there are no connected slots
屏蔽槽 (Beginner)

槽可以被暂时屏蔽, 意味着当信号被调用的时候,它虽然仍然链接着,但是却会被忽略.我们可以使用接口 blockunblock 来屏蔽或取消屏蔽. 下面是一个例子:

boost::signals::connection c = sig.connect(HelloWorld());
sig(); // Prints "Hello, World!"

c.block(); // block the slot
assert(c.blocked());
sig(); // No output: the slot is blocked

c.unblock(); // unblock the slot
sig(); // Prints "Hello, World!"
有作用范围的链接 (Intermediate)

boost::signals::scoped_connection 类指向一个在scoped_connection离开作用域后自动取消链接的信号-槽 . 如果一个链接是暂时的话, 这个能力就很有用了. 比如:

{
  boost::signals::scoped_connection c = sig.connect(ShortLived());
  sig(); // will call ShortLived function object
}
sig(); // ShortLived function object no longer connected to sig
取消相等的槽 (Intermediate)

如果一个仿函数支持==运算符, 那么我们就可以使用disconnect接口的一种形式来取消等于给定仿函数的链接槽.比如:

Preferred syntaxPortable syntax
void foo();
void bar();

signal<void()> sig;

sig.connect(&foo);
sig.connect(&bar);

// disconnects foo, but not bar
sig.disconnect(&foo);
void foo();
void bar();

signal0<void> sig;

sig.connect(&foo);
sig.connect(&bar);

// disconnects foo, but not bar
sig.disconnect(&foo);
自动链接管理 (Intermediate)

Boost.Signals 能够自动追踪参与信号-槽链接的对象的生命周期, 包括当参与槽调用的对象被销毁后自动取消链接. 比如, 考虑一个简单的交货服务器.客户端链接到一个新的为所有链接上客户提供新闻的提供者,这个交货服务器可能这样被构造:

Preferred syntaxPortable syntax
class NewsItem { /* ... */ };

boost::signal<void (const NewsItem&)> deliverNews;
class NewsItem { /* ... */ };

boost::signal1<void, const NewsItem&> deliverNews;

想要接受新闻的客户端只需提供一个链接deliverNews 信号的能接受news items的仿函数. 比如我们的程序需要一个特殊的消息空间:

struct NewsMessageArea : public MessageArea
{
public:
  // ...

  void displayNews(const NewsItem& news) const
  {
    messageText = news.text();
    update();
  }
};

// ...
NewsMessageArea newsMessageArea = new NewsMessageArea(/* ... */);
// ...
deliverNews.connect(boost::bind(&NewsMessageArea::displayNews, 
                                newsMessageArea, _1));

然而,如果用户关闭了消息空间,销毁了newsMessageArea对象的时候, deliverNews能知道什么呢? 最大的可能是段错误.然而, 使用boost.Signals的时候, 你只需要使NewsMessageArea变得可以追踪, 然后含有NewsMessageArea的槽就会自动的取消链.

通过继承boost::signals::trackable,NewsMessageArea就会变得可以被追踪:

struct NewsMessageArea : public MessageArea, public boost::signals::trackable
{
  // ...
};

到目前为止,在信号-槽链接中使用trackable的对象有一个显著的限制: 使用Boost.Bind创立的仿函数能被理解和追踪, 比如传递给boost::bind的指针或者引用.

警告: 使用者自定义的仿函数或者来自其他库的仿函数( 比如 Boost.Function or Boost.Lambda) 并没有实现可追踪的仿函数接口, 并且会被悄悄的忽略掉.

什么时候发生取消链接? (Intermediate)

下面的条件发生的时候, 信号-槽就会取消链接:

  • 显式的使用disconnect函数取消链接,或者间接的使用信号的disconnect接口或者scoped_connected析构的时候.

  • 一个绑定到槽的可追踪的对象被销毁.

  • 信号被销毁.

这些事件随时都可能发生, 不会打断正在被调用的信号序列. 如果一个信号-槽组合在信号被调用的时候被取消链接的话, 调用序列会继续执行,但是不会执行被取消的槽.另外,如果一个信号在信号被调用的时候销毁了,那么它的调用序列会执行完,但是不能直接访问. 

Signals may be invoked recursively (e.g., a signal A calls aslot B that invokes signal A...). The disconnection behavior doesnot change in the recursive case, except that the slot callingsequence includes slot calls for all nested invocations of thesignal.

传递槽 (Intermediate)

Boost.Signals的槽是由任意的仿函数创建的, 因此没有固定的类型.然而一个常见的需求是使用不是模板的接口来传递槽. 槽可以被每一个特定信号类型的slot_type来传递, 任何符合信号签名的仿函数都可以传递为slot_type的参数, 比如:

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

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

private:
  OnClick onClick;
};

void Button::doOnClick(
      const OnClick::slot_type& slot
    )
{
  onClick.connect(slot);
}

void printCoordinates(long x, long y)
{
  std::cout << "(" << x << ", " << y << ")\n";
}

void f(Button& button)
{
  button.doOnClick(&printCoordinates);
}
class Button 
{
  typedef boost::signal2<void,int,int> OnClick;

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

private:
  OnClick onClick;
};

void Button::doOnClick(
      const OnClick::slot_type& slot
    )
{
  onClick.connect(slot);
}

void printCoordinates(long x, long y)
{
  std::cout << "(" << x << ", " << y << ")\n";
}

void f(Button& button)
{
  button.doOnClick(&printCoordinates);
}

这个doOnclick接口限制等同于onClick信号的connect接口,但是doOnclick接口的细节可以隐藏到一个具体的实现文件里面.

例子: Document-View

可以使用信号来实现复杂的Document-View架构.  document含有一个链接到每一个View的信号. 下面的Document定义了支持多View的简单的文本Document .注意它保存了一个信号,所有的View都连接到这个信号.

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

public:
    Document()
    {}

    connection_t connect(signal_t::slot_function_type subscriber)
    {
        return m_sig.connect(subscriber);
    }

    void disconnect(connection_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;
};

接着我们定义View的基类,其他的View都可以基于这个类. 当然不是一定要,但是这样就使得Document-View logic逻辑和它本身的逻辑分离了.注意构造函数仅仅是链接了他们而析构函数取消了链接.

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

    virtual ~View()
    {
        m_document.disconnect(m_connection);
    }

    virtual void refresh(bool bExtended) const = 0;

protected:
    Document&               m_document;

private:
    Document::connection_t  m_connection;
};
  

最终我们定义了View, 下面的View只是简单的展示了文档的文本内容.

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

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

此外我们可以提供一个View来展示文本内容的二进制数值

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

    virtual void refresh(bool bExtended) const
    {
        const std::string&  s = m_document.getText();

        std::cout << "HexView:";

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

        std::cout << std::endl;
    }
};

为了能链接起整个例子来,这里提供了一个简单的main函数.启动了两个View后改变了Document的内容:

int main(int argc, char* argv[])
{
    Document    doc;
    TextView    v1(doc);
    HexView     v2(doc);

    doc.append(argc == 2 ? argv[1] : "Hello world!");
    return 0;
}

The complete example source, contributed by Keith MacDonald, is available in libs/signals/example/doc_view.cpp.

连接对应的 Signals库

Boost.Signals库的一部分被编译成必须要链接到你使用Signal的的程序中的二进制文件. 请参考Getting Started . 你需要链接boost_signals库.



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值