Friday, April 17, 2009

Gtkmm 实现信号使用 libsigc++

这个 libsigc++ 只有一个终极目标,通过 C++ 的 template 机制实现 object 之间 signal/slot 功能。与 Qt 依赖 QMetaObject 不同在于,前者 hack C++ 的 template,而后者其实通过什么字符串等实现的,我们在这里仅仅实现一个简单的通信例子,介绍使用方法。我们将在后面的文章比较这两种实现需要的开销,细节。 整体上来看 libsigc++ 实现通信机制要简单很多,下面是 counter.hpp:
#ifndef COUNTER_HPP
#define COUNTER_HPP
#include <iostream>
#include <sigc++/sigc++.h>

class Counter {
int value ;
public:
sigc::signal<void, int> valueChanged ;
void setValue( int ) ;
Counter( int = 0 ) ;

friend std::ostream& operator<< ( std::ostream&, const Counter& ) ;
} ;

#endif
这里和 Qt 不同之处是需要通过 composite 一个 sigc::signal 的对象从而使得该类获得 signal/slot 处理能力,Qt 是依靠继承 QObject 获得(使用 Q_OBJECT 只是为了获得新的 QMetaObject)。下面是实现代码 counter.cpp:
#include <iostream>
#include "counter.hpp"

void
Counter::setValue( int val )
{
value = val ;
// emit the signal
valueChanged.emit( value ) ;
}

Counter::Counter( int val ) : value( val ) {}

std::ostream&
operator<< ( std::ostream& os, const Counter& c )
{
return os << c.value ;
}
这里通过 sigc::signal::emit 方法或者 sigc::signal::operator() 发送信号。最后是主文件 test_sigc.cpp:
#include <iostream>
#include <iomanip>
#include "counter.hpp"

using namespace std ;

int
main( int argc, char *argv[] )
{
Counter a, b( 10 ) ;

cout << "We have two counters, " << a
     << " and " << b << endl ;

a.valueChanged.connect( sigc::mem_fun( b, &Counter::setValue ) ) ;

a.setValue( 12 ) ;
cout << "We have two counters, " << a
     << " and " << b << endl ;

b.setValue( 5 ) ;
cout << "We have two counters, " << a
     << " and " << b << endl ;

return 0 ;
}
这里调用了 sigc::signal::connect 方法连接到一个 slot上,该 slot 是使用 sigc::mem_fun 产生的一个调用指定对象成员函数的 functor。其实 libsigc++ 提供了远远不止这些功能,我们将在下面仔细介绍其他的功能。 编译 libsigc++ 程序可以用 pkg-config,如
$ g++ -o test_sigc test_sigc.o counter.o `pkg-config sigc++-2.0 --libs`
通过以上例子我们可以大致获得一个使用该库的印象,需要 signal 功能的类通过 composition 或者继承(感觉似乎不大好,也许应该 protected 继承?)加入 sigc::signal<> 对象,该模板第一个参数是 signal 返回值,后面写上的话是 signal 的参数,可见通过 template 的检查保证了 type safe。之后使用 sigc::signal::connect 连接到一个 slot 上,该 slot 可以用 sigc::mem_fun() 创建(使用对象的成员函数)或者 sigc::fun_ptr() 创建(使用一般函数地址)。在该库编写的时候合适的地方通过调用 sigc::signal::emit() (或者重载的 operator())发出 signal。 简而言之,C++ 的 virtual function 为对象在继承过程中实现多态提供了基础,是一个同类函数同名操作的函数钩子;signal/slot 机制是为了对象之间互相通信留下的函数钩子。只是前者通过 vtable 很容易实现,后者需要依赖 template 和 functor 等比较高级的方式实现。

No comments: