C++ Primer 学习笔记_57_类与数据抽象 --管理指针成员(二)

2014-11-24 12:26:58 · 作者: · 浏览: 1
asPtr构造函数执行完毕后,HasPtr对象指向一个新分配的U_Ptr对象,该U_Ptr对象存储给定指针。新U_Ptr中的使用计数为1,表示只有一个HasPtr对象指向它。

复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,该U_Ptr对象的使用计数加1。

析构函数将检查U_Ptr基础对象的使用计数。如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象。


4、赋值与使用计数

赋值操作符比复制构造函数要复杂一点:

HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
    ++ rhs.ptr -> use;
    if ( -- ptr -> use == 0)
        delete ptr;
    ptr = rhs.ptr;
    val = rhs.val;

    return *this;
}

在这里,首先将右操作数中的使用计数加1,然后将左操作数对象的使用计数减1并检查这个使用计数。像析构函数中那样,如果这是指向U_Ptr对象的最后一个对象,就删除该对象,这会依次撤销int基础对象。将左操作数中的当前值减1(可能撤销该对象)之后,再将指针从rhs复制到这个对象。

这个赋值操作符在减少左操作数的使用计数之前使rhs的使 用计数加1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是U_Ptr基础对象的使用计数加1之后立即减 1。


5、改变其他成员

class HasPtr
{
public:
    int *get_ptr() const
    {
        return ptr -> ip;
    }
    int get_val() const
    {
        return val;
    }

    void set_ptr(int *p)
    {
        ptr -> ip = p;
    }
    void set_val(int i)
    {
        val = i;
    }

    int get_ptr_val() const
    {
        return *(ptr -> ip);
        // or return * ptr->ip;
    }
    void set_ptr_val(int i)
    {
        * ptr-> ip = i;
    }

private:
    U_Ptr *ptr;
    int val;
};

复制HasPtr对象时,副本和原对象中的指针仍指向同一基础对象,对基础对象的改变将影响通过任一 HasPtr对象所看到的值。然而,HasPtr的用户无须担心悬垂指针。只要他们让HasPtr类负责释放对象,HasPtr类将保证只要有指向基础对象的HasPtr对象存在,基础对象就存在。


【建议:管理指针成员 P425值得仔细品读】

具有指针成员的对象一般需要定义复制控制成员。如果依赖合成版本,会给类的用户增加负担。用户必须保证成员所指向的对象存在,只要还有对象指向该对象

为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数、赋值操作符和析构函数。这些成员可以定义指针成员的指针型行为或值型行为。

值型类将指针成员所指基础值的副本给每个对象。复制构造函数分配新元素并从被复制对象处复制值,赋值操作符撤销所保存的原对象并从右操作数向左操作数复制值,析构函数撤销对象。

作为定义值型行为或指针型行为的另一选择,是使用称为“智能指针”的一些类。这些类在对象间共享同一基础值,从而提供了指针型行为。但它们使用复制控制技术以避免常规指针的一些缺陷。为了实现智能指针行为,类需要保证基础对象一直存在,直到最后一个副本消失。使用计数是管理智能指针类的通用技术。

管理指针的这些方法用得非常频繁,因此使用带指针成员类的程序员必须充分熟悉这些编程技术


//P425 习题13.24
class U_Ptr
{
    friend class HasPtr;
    int *ip;
    size_t use;
    U_Ptr(int *p): ip(p), use(1) { }
    ~U_Ptr()
    {
        delete ip;
    }
};

class HasPtr
{
public:
    HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { }

    HasPtr(const HasPtr &orig):
        ptr(orig.ptr), val(orig.val)
    {
        ++ptr->use;
    }
    HasPtr& operator=(const HasPtr&);

    ~HasPtr()
    {
        if (--ptr->use == 0)
            delete ptr;
    }

    int *get_ptr() const
    {
        return ptr->ip;
    }
    int get_int() const
    {
        return val;
    }

    void set_ptr(int *p)
    {
        ptr->ip = p;
    }
    void set_int(int i)
    {
        val = i;
    }

    int get_ptr_val() const
    {
        return *ptr->ip;
    }
    void set_ptr_val(int i)
    {
        *ptr->ip = i;
    }

private:
    U_Ptr *ptr;
    int val;
};

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    ++rhs.ptr->use;
    if (--ptr->use == 0)
        delete ptr;
    ptr = rhs.ptr;
    val = rhs.val;
    return *this;
}

三、定义值型类

复制值型对象时,会得到一个不同的新副本。对副本所作的改变不会反映在原有对象上,反之亦然。(类似于string)

class HasPtr
{
private:
    HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}

    //复制控制
    HasPtr(const HasPtr &rhs):ptr(new int(*rhs.ptr)),val(rhs.val) {}
    HasPtr &operator=(const HasPtr &rhs);
    ~HasPtr()
    {
        delete ptr;
    }

    int *get_ptr() const
    {
        return ptr;
    }

    int get_val() const
    {
        return val;
    }

    void set_ptr(int *p)
    {
        ptr = p;
    }
    void set_val(int i)
    {
        val = i;
    }

    int get_ptr_val()   const
    {
        return *ptr;
    }
    void set_ptr_val(int i) const
    {
        *ptr = i;
    }

public:
    int *ptr;
    int val;
};

复制构造函数不再复制指针,它将分配一个新的int对象,并初始化该对象以保存与被复制对象相同的值。每个对象都保存属于自己的int值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针

赋值操作符也因而不用分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:

HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
    *ptr = *rhs.ptr;
    val = rhs.val