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

模板与泛型编程

--一个泛型句柄类

引言:

【小心地雷】

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

前面示例的Sales_item和Query两个类的使用计数的实现是相同的。这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数。原本不相关的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)返回h,h是使用计数式句柄的成员;

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;
};
/**
*其他操作与前相似,在此不再赘述
*/

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类,布布扣,bubuko.com

时间: 2024-10-03 22:37:11

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

C++ Primer 学习笔记_77_模板与泛型编程 --实例化

模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化. 1.类的实例化 当编写Queue<int>qi时,编译器自己主动创建名为Queue<int>的类.实际上,编译器通过又一次编写Queue模板,用类型int取代模板形參的每次出现而创建Queue<int

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

模板与泛型编程 --类模板成员[续1] 二.非类型形参的模板实参 template <int hi,int wid> class Screen { public: Screen():screen(hi * wid,'#'), cursor(hi * wid),height(hi),width(wid) {} //.. private: std::string screen; std::string::size_type cursor; std::string::size_type height

C++ Primer 学习笔记_82_模板与泛型编程 --类模板成员[续2]

模板与泛型编程 --类模板成员[续2] 六.完整的Queue类 Queue的完整定义: template <typename Type> class Queue; template <typename Type> ostream &operator<<(ostream &,const Queue<Type> &); template <typename Type> class QueueItem { friend clas

C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模板的定义. 泛型编程与面向对象编程一样,都依赖于某种形式的多态性.面向对象编程中的多态性在执行时应用于存在继承关系的类.我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异.仅仅要使用基类的引用或指针,基类类型或派生类类型的对象就能够使用同样的代码. 在泛型编程中,我们所编写的类和函数能够

C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一些情况下,能够利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数. compare函数和 Queue类都是这一问题的好样例:与C风格字符串一起使用进,它们都不能正确工作. compare函数模板: template <typename Type> int compare(cons

C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还可以只特化push和pop成员.我们将特化push成员以复制字符数组,并且特化pop成员以释放该副本使用的内存: template<> void Queue<const char *>::push(const char *const &val) { char *new_item = new char[sizeof(val) + 1]; strncpy(new_item,val,sizeof(

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不立即产生代码.只有在用到模板时,如果调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调用函数时[不是模板],编译器只需看到函数的声明.类似的,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的.因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中. 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码.当调用函数模板或类模板的成员函

C++ Primer 学习笔记_80_模板与泛型编程 --类模板成员

模板与泛型编程 --类模板成员 引言: 这一节我们介绍怎样实现前面提到的Queue模板类. 标准库将queue实现为其他容器之上的适配器.为了强调在使用低级数据结构中设计的编程要点,我们将Queue实现为链表.实际上,在我们的实现中使用标准库可能是个更好的决定!!-_-. 1.Queue的实现策略 如图所示,我们实现两个类: 1)QueueItem类表示Queue的链表中的节点,该类有两个数据成员item和next: a. item保存Queue中元素的值,它的类型随Queue的每个实例而变化:

C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

模板与泛型编程 --重载与函数模板 引言: 函数模板可以重载:可以定义有相同名字但参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数. 但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性. 一.函数匹配与函数模板 如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下: 1.为这个函数名建立候选函数集合,包括: a.与被调用函数名字相同的任意普通函数. b.任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配