首页 > 代码库 > C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程

--一个泛型句柄类



引言:

【小心地雷】

这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承模板。在熟悉了这些特性之后再研究这个例子也许会帮助。另一方面,这个例子还能很好地测试你对这些特性的理解程度。



前面示例的Sales_itemQuery两个类的使用计数的实现是相同的。这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数。原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化。至于是公开还是隐藏下层的继承层次,句柄可以保持不同。



一、定义句柄类

Handle类行为类似于指针:复制Handle对象不会复制基础对象,复制之后,两个Handle对象将引用同一基础对象。要创建Handle对象,用户需要传递属于Handle管理的类型(或从该类型派生的类型)动态分配对象的地址,从此刻起,Handle将“拥有”这个对象。而且,一旦不再有任意Handle对象与该对象关联,Handle类将负责删除该对象

Handle类:

template <class T> class Handle
{
public:
    Handle(T *p = 0):ptr(p),use(new size_t(1)) {}
    Handle(const Handle &h):ptr(h.ptr),use(h.use)
    {
        ++ *use;
    }
    Handle &operator=(const Handle &rhs);
    ~Handle()
    {
        rem_ref();
    }

    T &operator*();
    T *operator->();
    const T &operator*() const;
    const T *operator->() const;

private:
    T *ptr;
    size_t *use;

    void rem_ref()
    {
        if (-- *use == 0)
        {
            delete use;
            delete ptr;
        }
    }
};


赋值操作符:

template <class Type>
Handle<Type> &Handle<Type>::operator=(const Handle<Type> &rhs)
{
    ++ *rhs.use;
    rem_ref();
    ptr = rhs.ptr;
    use = rhs.use;

    return *this;
}

解引用操作符和成员访问操作符[+P562习题16.45]

如果Handle没有绑定到对象,则试图访问对象时将抛出一个异常:

template <typename Type>
Type &Handle<Type>::operator*()
{
    if (ptr)
    {
        return *ptr;
    }
    throw std::runtime_error("dereference of unbound Handle");
}
template <typename Type>
Type *Handle<Type>::operator->()
{
    if (ptr)
    {
        return ptr;
    }
    throw std::runtime_error("access through unbound Handle");
}

template <typename Type>
const Type &Handle<Type>::operator*() const
{
    if (ptr)
    {
        return *ptr;
    }
    throw std::runtime_error("dereference of unbound Handle");
}
template <typename Type>
const Type *Handle<Type>::operator->() const
{
    if (ptr)
    {
        return ptr;
    }
    throw std::runtime_error("access through unbound Handle");
}

二、使用句柄

我们希望Handle类能够用于其他类的内部实现中

一个简单的示例:通过分配一个int对象,并将一个Handle对象绑定到新分配的int对象而说明Handle的行为:

{
    Handle<int> hp(new int(42));
    {
        Handle<int> hp2 = hp;
        cout << *hp << " " << *hp2 << endl; //42 42

        *hp2 = 10;
    }
    cout << *hp << endl;    //10
}

即使是Handle的用户分配了int对象,Handle析构函数也将删除它。在外层代码末尾最后一个Handle对象超出作用域时,删除该int对象。

使用Handle对象对指针进行使用计数

可以重新实现Sales_item类,在类中使用Handle,该类的这个版本定义相同的接口,但可以通过用Handle<Item_base>对象代替Item_base指针而删除复制控制成员:

class Sales_item
{
public:
    Sales_item():h() {}
    Sales_item(const Item_base &item):h(item.clone()) {}

    const Item_base &operator*() const
    {
        return *h;
    }
    const Item_base *operator->() const
    {
        return h.operator -> ();
    }

private:
    Handle<Item_base> h;
};

因为Sales_item的这个版本没有指针成员,所以不需要复制控制成员Sales_item的这个版本可以安全的使用合成的复制控制成员。管理使用计数和相关Item_base对象的工作在Handle内部完成

因为接口没变,所以不需要改变使用Sales_item类的代码。如:

double Basket::total() const
{
    double sum = 0.0;
    for (const_iter iter = items.begin();
            iter != items.end();
            iter = items.upper_bound(*iter))
    {
        sum += (*iter)->net_price(items.count(*iter));
    }

    return sum;
}

分析:

        sum += (*iter)->net_price(items.count(*iter));

1(*iter)返回hh是使用计数式句柄的成员;

2)因此,(*iter)->使用句柄类的重载箭头操作符;

3)编译器计算h.operator->(),获得Handle对象保存的Item_base指针;

4)编译器对该Item_base指针解引用,并调用指针所指向对象的Item_base成员。

//P564 习题16.51
class Query
{
    friend Query operator~(const Query &);
    friend Query operator|(const Query &,const Query &);
    friend Query operator&(const Query &,const Query &);
public:
    Query(const string &);

    set<TextQuery::line_no> eval(const TextQuery &t) const
    {
        return h -> eval(t);
    }
    ostream &display(ostream &os) const
    {
        return h -> display();
    }

private:
    Query(Query_base *query):h(query) {}
    Handle<Query_base> h;
};
/**
*其他操作与前相似,在此不再赘述
*/