UP | HOME

boost阅读笔记

Table of Contents

boost有一大堆乱七八糟的好东西。我在这里随手记一些阅读boost文档的笔记,仅供自己备忘。不能保证有始有终,不能保证完全正确……总之什么都不保证。随便看看吧,如果对你有帮助的话。当然,如果您能指出我的错误就辛苦您跟我说一声,非常感谢.

说明一下版权:由于所有文档基本归根到底都引用自boost的文档,这里的boost系列读书笔记都遵守boost的文档License。

boost::any

随手写一下boost::any的读书笔记。

先说明一下参考文档,在此感谢他们的作者。

  • boost::any的官方文档
  • flier @ newsmth.org在Programming版发的“boost::any”

C++是强类型语言,对变量类型与变量类型的转换要求非常苛刻。另外,C++的继承机制不是自封闭的,也就是说,没有类似于Java/Smalltalk 中的Object公用基类(当然,公用基类体制似乎也被B.S鄙视,呵呵),所以常常需要一个容器来保存各种类型的变量。

举个简单的例子,同一个std::map中的key和value的类型强制要求相同,因此,我们需要一个generic type来包装所有的变量。

boost::any的作者认为,所谓generic type有三个层次的解读:

  1. 类似variant类型一样,可以保存一个int(5)进去,读出一个(string)5出来。在Windows的实现中,VARIANT是一个巨大的联合。这种实现灵活但是效率较低。
  2. 区别对待所包含变量的类型,也就是说,保存一个(int)5,在保存和读取过程中不会被显式转换或者隐式转换为其他类型诸如(double)5.0,这样 做的好处主要是类型安全并且效率较高。
  3. 对包含的类型不加区别,例如把所有的值强制转换为void* 保存,读取时再由程序员判断类型。我看过的一个基于boost::lexical _ cast()的Variable Map的实现,也类似于这种类型。这种做法的效率最高但是无法保证类型安全。

boost::any选择了第二个层面的设计思路。用户可以将任何变量保存入any中,并且不改变其值的类型。看一下boost::any其中一个构造函数我们就知道这是怎么实现的了:

template<typename valueType> any(const valueType & value):content(new holder<valueType>(value))
{
}

类placeholder只是一个接口,而类holder是接口的实现,利用模板来实现保存各种变量的容器。

boost::any可以用type()方法获取所保存变量的类型,返回的是std::typeinfo的实例;可以用clone()方法获取所保存变量的副本。

对于any来说,除了保存变量外的另一个重要操作就是取出变量。boost::any提供了主动/被动类型检测。

主动类型检测就是利用type()方法,例子摘自boost的文档:

bool is_int(const boost::any& operand)
{
    return operand.type() == typeid(int);
}

被动类型检测就是利用anycast()函数,例子摘自flier的读书笔记:

boost::any str = string("12345");
try
{
    cout<<boost::any_cast<int>(str)<<endl;
}
catch(boost::bad_any_cast e)
{
    cerr<<e.what()<<endl;
}

看起来都挺丑陋的,没办法,谁让C++对RTTI的支持有限呢,哪天标准库支持了reflection就好了。虽然我也知道很难:(

对放进any中的变量,还是有一定要求的:

  • 可以被拷贝构造。
  • 可被赋值。
  • The destructor for a Value Type upholds the no-throw exception-safety guarantee(不知道怎么翻译更准了)

也就是说,类似boost::scoped _ ptr,boost::scoped _ array就不能被放进any里了。

感慨一下,boost的源代码写得真漂亮啊……在看看P.J.Plauger的STL实现,简直是一堆乱码:(

boost::signal

以前QT用得比较多,对signal/slot机制比较有兴趣,顺便看看boost的实现。

参考资料:boost.signals的官方文档,boost相关部分源代码。

名词解释:

  • signal:信号是多重目标的回调者,在类似系统中也称为事件或者发布者(ft,这中文我都看不懂。原文:Signals represents callbacks with multiple targets, and are also called publisher or events in similar systems.)
  • slot:回调函数的接受者,也称为事件的目标或注册者。(算了,后面我还是放弃翻译好了。原文:Signals are connected to some set of slots, which are callback receivers (also called event targets or subscribers), which are called when the signal is "emitted.")

建立起连接的signals和slots会在其生命期内保持联系,如果某个信号被销毁了,其对应的slot(中文翻译成“槽”,想不通)也会被销毁。酱紫调用者就不用费心去管理signals和slots及其之间联系的生命期了。

boost文档里给出了这么一个Hello World:

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

// ...
// 定义一个signal
boost::signal<void()> sig;

//和helloWorld这个slot连接
helloWorld hello;
sig.connect(hello);

//调用所有与之连接的slot
sig();

signal可以和多个slot建立连接,比如: sig.connect(a); sig.connect(b); ……

当触发一个signal时,如果对应的slot调用未定义的话可能会出现不可预测的问题,为解决该问题,slot的调用可以按照给定的顺序进行: sig.connect(1,a); sig.connect(2,b); ……

这里向connect传递boost::signal::在默认情况下,signal按照连接的顺序调用对应的slot,当然,这里的连接顺序就要靠用户自己保证了。

我们一样可以向slot传递参数,并且可以通过指针从slot中获取数据。例子仍然来自boost文档。

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

对应的signal: boost::signal sig;

连接signal和slot: sig.connect(&printsum);

调用: sig(5,3);

signal也是有返回值的,直接copy文档中的例子:

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;

在上面的程序中,sig(5,3)的返回值就是2。在默认情况下,signal的调用返回值其实就是最后一个slot的返回值。

实际上,signal通过一个组合器来组织调用的slot,signal的返回值其实就是这个组合器的返回值。默认组合器返回的是最后一个slot的返回值,用户也可以定义自己的组合器。boost文档中的例子如下:

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


//从上面的代码可以看出,组合器其实也就是一个仿函数。
//下面我们看看如何调用这个组合器:

boost::signal<float (float x, float y), maximum<float> > sig;
                                                     //~~小心别连起来了

在这个组合器之下,signal返回的就是所有slot返回值中的最大值。

有时我们可能需要一个获得所有slot返回值的组合器。例子来自boost文档:

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, 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, " "));

这里将会打印出所有slot的返回值。

接下来看看如何管理signal和slot之间的联系。

boost::signal::connection是connect函数的返回值,它保存了signal到slot之间的联系。少废话了,看例子吧,boost文档里的。

boost::signals::connection c = sig.connect(helloWorld());
if (c.connected()) 
{ //此时仍然保持连接状态
 sig();  
}

c.disconnect(); //断开连接  
assert(!c.connected()); 断言连接已经断开

sig(); //什么也不干

我们还可以利用scoped _ connection在域结束的时候中断signal和slot之间的连接。例子:

{
    boost::signals::scoped_connection c = sig.connect(shortLived());
    sig(); // 调用相应slot
}
sig() //ok,这里断开signal和slot的连接

断开signal的某一个slot连接

void foo();
void bar();

signal<void()> sig;

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

//断开foo而不是bar
sig.disconnect(&foo);

在signal/slot机制中包含的类中,boost::signal会自动跟随类的生命期。

在下列情况中,signal和slot将断开:

  • 显式调用了disconnect
  • scopedconnection离开了作用域
  • 与slot绑定的类被析构了
  • signal被析构了

附带说一下,信号是可以被递归调用的,比如说信号A激发了信号B,而信号B又激发了信号A。

从前面的例子开来,slot似乎可以是任意的仿函数,但是还是有一些限制的,一般我们要求,通过connect接口传递的slot不能是模板函数。

除了通过connect接口传送slot外,我们还可以使用slottype。例子如下:

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

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

private:
 onClick m_onClick;
};

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

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

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

酱紫看起来就有点像MFC了,呵呵。

boost::smart _ ptr

惯例,参考文献先说吧:

  • :filer@ newsmth.org在programming的《boost::smart _ ptr》
  • boost文档smart _ ptr部分,写得非常详细
  • boost::smart _ ptr源代码

据说有1000个程序员就有1001个智能指针的实现,现在看看boost的实现。指针的使用一向是C/C++程序设计的重点与难点,用得好可以灵活地实现许多精巧的功能,用得不好会让整个系统错误百出。指针使用的难点就在于其生命期的管理。现代高级语言的发展趋势是对开发者屏蔽了指针的生命期管理,例如C#和Java都提供了GC机制。但是根据C++的设计哲学,用户决不能为他们不使用的功能付出额外的代价,因此在可见的未来中,我们是别指望在C++标准中获得GC机制了。

智能指针可以帮助程序员管理指针的生命期,避免内存泄露。boost提供了5个功能不同的智能指针,scoped _ ptr/scoped _ array是一对非常纯粹的智能指针,不允许相互赋值和复制,仅仅用在顺序控制语句中作为指针的唯一所有者,管理指针的生命周期,以便于在发生异常时回收资源。前者管理单个的对象,后者管理数组对象。这两个指针由于没有拷贝构造函数,不能被用于标准容器中。 shared _ ptr/shared _ array在生命期管理中使用了引用计数机制,允许在传递智能指针后调用者和被调用者都拥有对指针的所有权。 weak _ ptr是对shared _ ptr的补充,它允许使用一种 weak连接,即拥有指针的使用权但不拥有所有权,通常和shared _ ptr配合使用,因此在使用weak _ ptr前必须检测指针是否有效。

boost文档对如何使用智能指针说得非常清楚,这里不多说了。接下来看看实现吧。先看看#boost/scoped _ ptr.hpp。

template<class T> class scoped_ptr
{
private:

    T * ptr;

    scoped_ptr(scoped_ptr const &);
    scoped_ptr & operator=(scoped_ptr const &);

    typedef scoped_ptr<T> this_type;

//这里可以看出,scoped_ptr禁止了拷贝构造和赋值操作。

public:
    typedef T element_type;
    explicit scoped_ptr(T * p = 0): ptr(p) {}
    explicit scoped_ptr(std::auto_ptr<T> p): ptr(p.release()) {}

//以上的代码把相关的预处理和异常机制去掉了,可以更清楚的看出scoped_ptr的构造函数。
//scoped_ptr可以从普通指针构造,也可以从std::auto_ptr中构造。

    ~scoped_ptr() // never throws
    {
        #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
        boost::sp_scalar_destructor_hook(ptr);
        #endif
        boost::checked_delete(ptr);
    }
//以上是析构函数,不必多说。下面看看checked_delete的源代码,位于boost/checked_delete.hpp。

template<class T> inline void checked_delete(T * x)
{
    // intentionally complex - simplification causes regressions
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete x;
}

checked _ delete代码中用了这么一个技巧。我们知道,对于不完整类型的指针: …… class incomplete; incomplete* p; delete p; …… 在这里,p并未指向一个实例且未指向0,因此delete p是危险的,但根据编译器不同,这里可能只会给出一个warning而不是error。因此,cheched _ delete代码中利用了“typedef char typemustbecomplete[ sizeof(T)? 1: -1 ];”,当p未指向实例时,sizeof(T)给出的是0,根据代码,-1是不能作为数组的size的,因此,这里相当于强制编译器给出error而不是 warning。

接着copy boost的源代码把:

    void reset(T * p = 0) // never throws
    {
        BOOST_ASSERT(p == 0 || p != ptr); // catch self-reset errors
        this_type(p).swap(this);
    }

    T & operator*() const // never throws
    {
        BOOST_ASSERT(ptr != 0);
        return *ptr;
    }

    T * operator->() const // never throws
    {
        BOOST_ASSERT(ptr != 0);
        return ptr;
    }

    T * get() const // never throws
    {
        return ptr;
    }

    bool operator! () const // never throws
    { 
        return ptr == 0;
    }

    void swap(scoped_ptr & b) // never throws
    {
        T * tmp = b.ptr;
        b.ptr = ptr;
        ptr = tmp;
    }
};
//scoped_ptr类的定义到此结束,上面的代码没啥可说的。接下来是两个简单的辅助函数。

template<class T> inline void swap(scoped_ptr<T> & a, scoped_ptr<T> & b) // never throws
{
    a.swap(b);
}

// get_pointer(p) is a generic way to say p.get()

template<class T> inline T * get_pointer(scoped_ptr<T> const & p)
{
    return p.get();
}

scoped _ ptr的代码非常简单。scoped _ array的代码和scoped _ ptr基本一致,不同的是,scoped _ ptr重载了->和*运算符,而scoped _ array重载了[]运算符。

shared _ ptr和shared _ array的代码较为复杂,它们利用引用计数机制实现了指针的赋值和复制功能。引用计数功能实现代码在 boost/detail/shared _ count.hpp中。该文件中实现了两个计数器,shared _ count和weak _ count,前者复制 /析构时调用counted _ base的add _ ref()/release(),后者只是调用weak _ add _ ref()/weak _ release ()。因为前者需要管理整个对象的生命期,在release()时如果use _ count减为0即调用虚函数dispose(),并完成实际析构工作;而后者只是释放counted _ base本身而已。注意这里用到了一个独立的函数self _ delete()完成释放工作,这是因为inline的 release()函数可能使用到不同的堆管理器。

shared _ count和weak _ count内包含了一个shared _ count _ base的成员指针,shared _ count _ base是一个纯虚类,其实现由派生类shared _ count _ base _ impl实现。

除了引用计数机制外,shared _ ptr和shared _ array的实现代码也较为简单。不赘述。

boost::array

先说参考文档:

  • boost相关文档
  • boost::array源代码

挑个软柿子捏一下。顺便说一下,boost::array已经进了C++标准的tr1了。我的gcc4.0已经支持,恭喜一下Nicolai.M.Josuttis.

STL的设计上希望用变长数组vector取代常规数组,但对于某些只需要固定长度数组的情况显得“重”了一些。因此有了boost::array。 boost::array的最初代码来源于B.S的《The C++ Programming Language》中的carray。

看代码前先想想我们需要一个怎样的定长array:

  • 首先要能和现有数组相互转换。
  • 平均效率应该和现有数组差不多,否则就没有存在的意义了。
  • 提供符合STL规范的iterator和reverse _ iterator,能够与现有STL算法配接。
  • 操作接口与现有数组相同。
  • 尽管像int c5 = {1,2,3}这样的初始化方式存在种种弊端,但考虑用户的使用习惯,boost::array应该支持这种初始化方式。后面我们可以看见,boost::array在这里做了一定的折衷。

接下来看boost::array的源代码吧。

namespace boost {

template<class T, std::size_t N> //通过模板参数传递数组的长度
class array {
public:
T elems[N]; // fixed-size array of elements of type T

public:
// type definitions
//看过STL源代码的人对下面一堆的类型定义应该非常熟悉了。
typedef T value_type;
typedef T* iterator; //数组的iterator用普通指针即可
typedef const T* const_iterator;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;

//iterator
        iterator begin() { return elems; }
        const_iterator begin() const { return elems; }
        iterator end() { return elems+N; }
        const_iterator end() const { return elems+N; }
//反向iterator
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
        reverse_iterator rbegin() { return reverse_iterator(end()); }
        const_reverse_iterator rbegin() const {
            return const_reverse_iterator(end());
        }
        reverse_iterator rend() { return reverse_iterator(begin()); }
        const_reverse_iterator rend() const {
            return const_reverse_iterator(begin());
        }

接下来的代码没什么好看了,实现了一堆常规的接口,包括: operator[]:按照STL惯例,这里没有包含了rangecheck功能。 at():按照STL管理,这里包含了rangecheck功能。 front(),back():没什么好说的。 size(),empty(),maxsize():也没什么好说的。常规的比较函数,如>,<,==,!=之类的。 assign():给数组所有元素赋相同的值。 swap:交换两个数组,考虑到使用方便,swap有一个成员函数的实现和一个友员函数的实现。 data():以只读形式返回C形式的数组。 carray():返回C形式数组。

没了,boost::array的代码基本上没啥可说的,这个柿子太软了一点……

Author: Le Cao

Date: 2010-10-12 23:30:08 CST

HTML generated by org-mode TAG=7.01g in emacs 23