您的位置:首页 > 编程语言 > C语言/C++

c++ primer阅读笔记-13章-3

2014-04-11 21:47 162 查看
13.5. 管理指针成员

1、包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。

2、指针成员默认具有与指针对象同样的行为。然而,通过不同的复制控制策略,可以为指针成员实现不同的行为。大多数 C++ 类采用以下三种方法之一管理指针成员:

a:指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。

b:类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。

c:类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。

3、复制一个算术值时,副本独立于原版,可以改变一个副本而不改变另一个。复制指针时,地址值是可区分的,但指针指向同一基础对象。两个指针指向同一对象时,其中任意一个都可以改变共享对象的值。

4、因为类直接复制指针,会使用户面临潜在的问题:HasPtr 保存着给定指针。用户必须保证只要 HasPtr 对象存在,该指针指向的对象就存在:

int *ip = new int(42); // dynamically allocated int initialized to 42
HasPtr ptr(ip, 10);    // Has Ptr points to same object as ip does
delete ip;             // object pointed to by ip is freed
ptr.set_ptr_val(0); // disaster: The object to which Has Ptr points was freed!


这里的问题是 ip 和 ptr 中的指针指向同一对象。删除了该对象时,ptr 中的指针不再指向有效对象。然而,没有办法得知对象已经不存在了。

13.5.1. 定义智能指针类

1、智能指针除了增加功能外,其行为像普通指针一样。本例中让智能指针负责删除共享对象。用户将动态分配一个对象并将该对象的地址传给新的 HasPtr 类。用户仍然可以通过普通指针访问对象,但绝不能删除指针。HasPtr 类将保证在撤销指向对象的最后一个 HasPtr 对象时删除对象。

2、新的 HasPtr 类需要一个析构函数来删除指针,但是,析构函数不能无条件地删除指针。如果两个 HasPtr 对象指向同一基础对象,那么,在两个对象都撤销之前,我们并不希望删除基础对象。为了编写析构函数,需要知道这个 HasPtr 对象是否为指向给定对象的最后一个。

3、定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为 0 时,删除对象。使用计数有时也称为引用计数。

4、每次创建类的新对象时,初始化指针并将使用计数置为 1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至 0,则删除对象),并增加右操作数所指对象的使用计数的值(???)。最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至
0,则删除基础对象。

5、唯一的创新在于决定将使用计数放在哪里。计数器不能直接放在 HasPtr 对象中,为什么呢?考虑下面的情况:

int obj;
HasPtr p1(&obj, 42);
HasPtr p2(p1);  // p1 and p2 both point to same int object
HasPtr p3(p1);  // p1, p2, and p3 all point to same int object


如果使用计数保存在 HasPtr 对象中,创建 p3 时怎样更新它?可以在 p1 中将计数增量并复制到 p3,但怎样更新 p2 中的计数?

使用计数类

1、实现使用计数有两种经典策略,这里所用的方法中,需要定义一个单独的具体类用以封闭使用计数和相关指针:

// private class for use by HasPtr only
class U_Ptr {
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p): ip(p), use(1) { }
~U_Ptr() { delete ip; }
};


这个类的所有成员均为 private。我们不希望用户使用 U_Ptr 类,所以它没有任何 public 成员。将 HasPtr 类设置为友元,使其成员可以访问 U_Ptr 的成员。

2、尽管该类的工作原理比较难,但这个类相当简单。U_Ptr 类保存指针和使用计数,每个 HasPtr 对象将指向一个 U_Ptr 对象,使用计数将跟踪指向每个 U_Ptr 对象的 HasPtr 对象的数目。U_Ptr 定义的仅有函数是构造函数和析构函数,构造函数复制指针,而析构函数删除它。构造函数还将使用计数置为 1,表示一个 HasPtr 对象指向这个 U_Ptr 对象。

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

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

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

作为定义值型行为或指针型行为的另一选择,是使用称为“智能指针”的一些类。这些类在对象间共享同一基础值,从而提供了指针型行为。但它们使用复制控制技术以避免常规指针的一些缺陷。为了实现智能指针行为,类需要保证基础对象一直存在,直到最后一个副本消失。使用计数(第 13.5.1 节)是管理智能指针类的通用技术。同一基础值的每个副本都有一个使用计数。复制构造函数将指针从旧对象复制到新对象时,会将使用计数加 1。赋值操作符将左操作数的使用计数减 1 并将右操作数的使用计数加 1,如果左操作数的使用计数减至 0,赋值操作符必须删除它所指向的对象,最后,赋值操作符将指针从右操作数复制到左操作数。析构函数将使用计数减
1,并且,如果使用计数减至 0,就删除基础对象。

13.5.2. 定义值型类


1、处理指针成员的另一个完全不同的方法,是给指针成员提供值语义。具有值语义的类所定义的对象,其行为很像算术类型的对象:复制值型对象时,会得到一个不同的新副本。对副本所做的改变不会反映在原有对象上,反之亦然。string 类是值型类的一个例子。

2、要使指针成员表现得像一个值,复制 HasPtr 对象时必须复制指针所指向的对象:

/*
* Valuelike behavior even though HasPtr has a pointer member:
* Each time we copy a HasPtr object, we make a new copy of the
* underlying int object to which ptr points.
*/
class HasPtr {
public:
// no point to passing a pointer if we're going to copy it anyway
// store pointer to a copy of the object we're given
HasPtr(const int &p, int i): ptr(new int(p)), val(i) {}

// copy members and increment the use count
HasPtr(const HasPtr &orig):
ptr(new int (*orig.ptr)), val(orig.val) { }

HasPtr& operator=(const HasPtr&);
~HasPtr() { delete ptr; }
// accessors must change to fetch value from Ptr object
int get_ptr_val() const { return *ptr; }
int get_int() const { return val; }

// change the appropriate data member
void set_ptr(int *p) { ptr = p; }
void set_int(int i) { val = i; }

// return or change the value pointed to, so ok for const objects
int *get_ptr() const { return ptr; }
void set_ptr_val(int p) const { *ptr = p; }
private:
int *ptr;        // points to an int
int val;
};


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

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

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
// Note: Every HasPtr is guaranteed to point at an actual int;
//    We know that ptr cannot be a zero pointer
*ptr = *rhs.ptr;       // copy the value pointed to
val = rhs.val;         // copy the int
return *this;
}


换句话说,改变的是指针所指向的值,而不是指针。

即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: