复制控制(下)
- 消息处理示例
有些类为了做一些工作需要对复制进行控制。为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序。Message 类和 Folder 类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中。Message 上有 save 和 remove 操作,用于在指定 Folder 中保存或删除该消息。对每个 Message,我们并不是在每个 Folder 中都存放一个副本,而是使每个 Message 保存一个指针集(set),set 中的指针指向该 Message 所在的 Folder。每个 Folder 也保存着一些指针,指向它所包含的 Message。
创建新的 Message 时,将指定消息的内容但不指定 Folder。调用 save 将 Message 放入一个 Folder;复制一个 Message 对象时,将复制原始消息的内容和 Folder 指针集,还必须给指向源 Message 的每个 Folder 增加一个指向该 Message 的指针;将一个 Message 对象赋值给另一个,类似于复制一个 Message:赋值之后,内容和 Folder 集将是相同的。首先从左边 Message 在赋值之前所处的 Folder 中删除该 Message。原来的 Message 去掉之后,再将右边操作数的内容和 Folders 集复制到左边,还必须在这个 Folder 集中的每个 Folders 中增加一个指向左边 Message 的指针;撤销一个 Message 对象时,必须更新指向该 Message 的每个 Folder。一旦去掉了 Message,指向该 Message 的指针将失效,所以必须从该 Message 的 Folder 指针集的每个 Folder 中删除这个指针。//复制(复制构造函数)用来初始化一个空对象,赋值(赋值操作符)则是用来改变已经有内容(已被初始化过的)的对象为新内容。查看这个操作列表,可以看到,析构函数和赋值操作符分担了从保存给定 Message 的 Folder 列表中删除消息的工作。类似地,复制构造函数和赋值操作符分担将一个 Message 加到给定 Folder 列表的工作。我们将定义一对 private 实用函数完成这些任务。
- Message Class
class Message { public: // folders is initialized to the empty set automatically Message(const std::string &str = ""): contents (str) { } // copy control: we must manage pointers to this Message // from the Folders pointed to by folders Message(const Message&);//复制构造函数 Message& operator=(const Message&); ~Message(); // add/remove this Message from specified Folder's set of messages void save (Folder&); void remove(Folder&); private: std::string contents; // actual message text std::set<Folder*> folders; // Folders that have this Message // Utility functions used by copy constructor, assignment, and destructor: // Add this Message to the Folders that point to the parameter void put_Msg_in_Folders(const std::set<Folder*>&); // remove this Message from every Folder in folders void remove_Msg_from_Folders(); };
Message 类定义了两个数据成员:contents 是一个保存实际消息的 string,folders 是一个 set,包含指向该 Message 所在的 Folder 的指针。构造函数接受单个 string 形参,表示消息的内容。构造函数将消息的副本保存在 contents 中,并(隐式)将 Folder 的 set 初始化为空集。这个构造函数提供一个默认实参(为空串),所以它也可以作为默认构造函数。put_Msg_in_Folders 函数将自身 Message 的一个副本添加到指向给定 Message 的各 Folder 中,这个函数执行完后,形参指向的每个 Folder 也将指向这个 Message。复制构造函数和赋值操作符都将使用这个函数。remove_Msg_from_Folders 函数用于赋值操作符和析构函数,它从 folders 成员的每个 Folder 中删除指向这个 Message 的指针。
- Message类的复制控制
复制 Message 时,必须将新创建的 Message 添加到保存原 Message 的每个 Folder 中。这个工作超出了合成构造函数的能力范围,所以我们必须定义自己的复制构造函数:
Message::Message(const Message &m): contents(m.contents), folders(m.folders) { // add this Message to each Folder that points to m put_Msg_in_Folders(folders); }
复制构造函数将用旧对象成员的副本初始化新对象的数据成员。除了这些初始化之外(合成复制构造函数可以完成这些初始化),还必须用 folders 进行迭代,将这个新的 Message 加到那个集的每个 Folder 中。复制构造函数使用 put_Msg_in_Folder 函数完成这个工作。编写自己的复制构造函数时,必须显式复制需要复制的任意成员。显式定义的复制构造函数不会进行任何自动复制。像其他任何构造函数一样,如果没有初始化某个类成员,则那个成员用该成员的默认构造函数初始化。复制构造函数中的默认初始化不会使用成员的复制构造函数。
- put_Msg_in_Folders成员
put_Msg_in_Folders 通过形参 rhs 的成员 folders 中的指针进行迭代。这些指针表示指向 rhs 的每个 Folder,需要将指向这个 Message 的指针加到每个 Folder。函数通过 rhs.folders 进行循环,调用命名为 addMsg 的 Folder 成员来完成这个工作,addMsg 函数将指向该 Message 的指针加到 Folder 中。
// add this Message to Folders that point to rhs void Message::put_Msg_in_Folders(const set<Folder*> &rhs) { for(std::set<Folder*>::const_iterator beg = rhs.begin(); beg != rhs.end(); ++beg) (*beg)->addMsg(this); // *beg points to a Folder }
这个函数中唯一复杂的部分是对 addMsg 的调用:
(*beg)->addMsg(this); // *beg points to a Folder
那个调用以 (*beg) 开关,它解除迭代器引用。解除迭代器引用将获得一个指向 Folder 的指针。然后表达式对 Folder 指针应用箭头操作符以执行 addMsg 操作,将 this 传给 addMsg,该指针指向我们想要添加到 Folder 中的 Message。
- Message赋值操作符
赋值比复制构造函数更复杂。像复制构造函数一样,赋值必须对 contents 赋值并更新 folders 使之与右操作数的 folders 相匹配。它还必须将该 Message 加到指向 rhs 的每个 Folder 中,可以使用 put_Msg_in_Folders 函数完成赋值的这一部分工作。在从 rhs 复制之前,必须首先从当前指向该 Message 的每个 Folder 中删除它。我们需要通过 folders 进行迭代,从 folders 的每个 Folder 中删除指向该 Message 的指针。命名为 remove_Msg_from_Folders 的函数将完成这项工作。对于完成实际工作的 remove_Msg_from_Folders 和 put_Msg_in_Folders,赋值操作符本身相当简单:
Message& Message::operator=(const Message &rhs) { if (&rhs != this) { remove_Msg_from_Folders(); // update existing Folders contents = rhs.contents; // copy contents from rhs folders = rhs.folders; // copy Folder pointers from rhs // add this Message to each Folder in rhs put_Msg_in_Folders(rhs.folders); } return *this; }
赋值操作符首先检查左右操作数是否相同。查看函数的后续部分可以清楚地看到进行这一检查的原因。假定操作数是不同对象,调用 remove_Msg_from_Folders 从 folders 成员的每个 Folder 中删除该 Message。一旦这项工作完成,必须将右操作数的 contents 和 folders 成员赋值给这个对象。最后,调用 put_Msg_in_Folders 将指向这个 Message 的指针添加至指向 rhs 的每个 Folder 中。了解了 remove_Msg_from_Folders 的工作之后,我们来看看为什么赋值操作符首先要检查对象是否不同。赋值时需删除左操作数,并在撤销左操作数的成员之后,将右操作数的成员赋值给左操作数的相应成员。如果对象是相同的,则撤销左操作数的成员也将撤销右操作数的成员!即使对象赋值给自己,赋值操作符的正确工作也非常重要。保证这个行为的通用方法是显式检查对自身的赋值。
- remove_Msg_from_Folders成员
除了调用 remMsg 从 folders 指向的每个 Folder 中删除这个 Message 之外,remove_Msg_from_Folders 函数的实现与 put_Msg_in_Folders 类似:
// remove this Message from corresponding Folders void Message::remove_Msg_from_Folders() { // remove this message from corresponding folders for(std::set<Folder*>::const_iterator beg = folders.begin (); beg != folders.end (); ++beg) (*beg)->remMsg(this); // *beg points to a Folder }
- Message析构函数
剩下必须实现的复制控制函数是析构函数:
Message::~Message()
{
remove_Msg_from_Folders();
}
有了 remove_Msg_from_Folders 函数,编写析构函数将非常简单。我们调用 remove_Msg_from_Folders 函数清除 folders,系统自动调用 string 析构函数释放 contents,自动调用 set 析构函数清除用于保存 folders 成员的内存,因此,Message 析构函数唯一要做的是调用 remove_Msg_from_Folders。赋值操作符通常要做复制构造函数和析构函数也要完成的工作。在这种情况下,通用工作应在 private 实用函数中。
- 管理指针成员
包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。设计具有指针成员的类时,类设计者必须首先需要决定的是该指针应提供什么行为。将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。
指针成员默认具有与指针对象同样的行为。然而,通过不同的复制控制策略,可以为指针成员实现不同的行为。大多数 C++ 类采用以下三种方法之一管理指针成员:
指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制;类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针;类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。
- 一个带指针成员的简单类
我们将实现一个简单类,该类包含一个 int 值和一个指针:
// class that has a pointer member that behaves like a plain pointer class HasPtr { public: // copy of the values we're given HasPtr(int *p, int i): ptr(p), val(i) { } // const members to return the value of the indicated data member int *get_ptr() const { return ptr; } int get_int() const { return val; } // non const members to change the indicated 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_val() const { return *ptr; } void set_ptr_val(int val) const { *ptr = val; } private: int *ptr; int val; };
HasPtr 构造函数接受两个形参,将它们复制到 HasPtr 的数据成员。HasPtr 类提供简单的访问函数:函数 get_int 和 get_ptr 分别返回 int 成员和指针成员的值:set_int 和 set_ptr 成员则使我们能够改变这些成员,给 int 成员一个新值或使指针成员指向不同的对象。还定义了 get_ptr_val 和 set_ptr_val 成员,它们能够获取和设置指针所指向的基础值。
- 默认复制/赋值与指针成员
因为 HasPtr 类没有定义复制构造函数,所以复制一个 HasPtr 对象将复制两个成员:
int obj = 0; HasPtr ptr1(&obj, 42); // int* member points to obj, val is 42 HasPtr ptr2(ptr1); // int* member points to obj, val is 42
复制之后,ptr1 和 ptr2 中的指针指向同一对象且两个对象中的 int 值相同。但是,因为指针的值不同于它所指对象的值,这两个成员的行为看来非常不同。复制之后,int 值是清楚和独立的,而指针则纠缠在一起。具有指针成员且使用默认合成复制构造函数的类具有普通指针的所有缺陷。尤其是,类本身无法避免悬垂指针。
验证一下:
int main() { int obj = 0; HasPtr ptr1(&obj, 42); // int* member points to obj, val is 42 //调用了构造函数 HasPtr ptr2(ptr1); // int* member points to obj, val is 42 //调用了合成的复制构造函数 cout << ptr1.get_ptr_val() << endl; cout << ptr1.get_ptr() << endl; cout << ptr2.get_ptr_val() << endl; cout << ptr2.get_ptr() << endl; system("pause"); return 0; }
输出结果为:
- 指针共享同一对象
复制一个算术值时,副本独立于原版,可以改变一个副本而不改变另一个:
ptr1.set_int(0); // changes val member only in ptr1 ptr2.get_int(); // returns 42 ptr1.get_int(); // returns 0
输出结果:
复制指针时,(指针的)地址值是可区分的,但指针指向同一基础对象(指针的值是相同的)。如果在任一对象上调用 set_ptr_val,则二者的基础对象都会改变:
ptr1.set_ptr_val(42); cout << ptr1.get_ptr_val() << endl; cout << ptr2.get_ptr_val() << endl;
输出结果:
两个指针指向同一对象时,其中任意一个都可以改变共享对象的值。//其实ptr1和ptr2中的指针成员是两个成员,因为它们各自的地址不同,但是他们的值是相同的,即他们指向的位置是相同的。而指针是使用往往是改变指针指向的内容,而并非指针自身的值,所以当你改变一个指针指向的内容时,另外一个指针指向的内容也会随之改变。
- 可能出现悬垂指针
因为类直接复制指针,会使用户面临潜在的问题: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!
因为访问已经被释放的内存,所以会报错:
此外,如果再使用cout << ptr.get_ptr_val() << endl;访问已经释放了的指针的内容,则会得到其它数据:
这里的问题是 ip 和 ptr 中的指针指向同一对象。删除了该对象时,ptr 中的指针不再指向有效对象。然而,没有办法得知对象已经不存在了。
- 定义智能指针类//就是为了解决上面指针成员所出现的问题的
前面定义了一个简单类,保存一个指针和一个 int 值。其中指针成员的行为与其他任意指针完全相同。对该指针指向的对象所做的任意改变都将作用于共享对象。如果用户删除该对象,则类就有一个悬垂指针,指向一个不复存在的对象。除了使指针成员与指针完全相同之外,另一种方法是定义所谓的智能指针类。智能指针除了增加功能外,其行为像普通指针一样。本例中让智能指针负责删除共享对象。用户将动态分配一个对象并将该对象的地址传给新的 HasPtr 类。用户仍然可以通过普通指针访问对象,但绝不能删除指针。HasPtr 类将保证在撤销指向对象的最后一个 HasPtr 对象时删除对象。HasPtr 在其他方面的行为与普通指针一样。具体而言,复制对象时,副本和原对象将指向同一基础对象,如果通过一个副本改变基础对象,则通过另一对象访问的值也会改变。新的 HasPtr 类需要一个析构函数来删除指针,但是,析构函数不能无条件地删除指针。如果两个 HasPtr 对象指向同一基础对象,那么,在两个对象都撤销之前,我们并不希望删除基础对象。为了编写析构函数,需要知道这个 HasPtr 对象是否为指向给定对象的最后一个。
- 引入使用计数
定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为 0 时,删除对象。使用计数有时也称为引用计数。每次创建类的新对象时,初始化指针并将使用计数置为 1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至 0,则删除对象),并增加右操作数所指对象的使用计数的值。最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至 0,则删除基础对象。唯一的创新在于决定将使用计数放在哪里。计数器不能直接放在 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 中的计数?
// 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 的成员。尽管该类的工作原理比较难,但这个类相当简单。U_Ptr 类保存指针和使用计数,每个 HasPtr 对象将指向一个 U_Ptr 对象,使用计数将跟踪指向每个 U_Ptr 对象的 HasPtr 对象的数目。U_Ptr 定义的仅有函数是构造函数和析构函数,构造函数复制指针,而析构函数删除它。构造函数还将使用计数置为 1,表示一个 HasPtr 对象指向这个 U_Ptr 对象。
假定刚从指向 int 值 42 的指针创建一个 HasPtr 对象,可以画出这些对象,如下图:
如果复制这个对象,则对象如下图所示。新的 HasPtr 类保存一个指向 U_Ptr 对象的指针,U_Ptr 对象指向实际的 int 基础对象。必须改变每个成员以说明的 HasPtr 类指向一个 U_Ptr 对象而不是一个 int:
先看看构造函数和复制控制成员:
/* smart pointer class: takes ownership of the dynamically allocated * object to which it is bound * User code must dynamically allocate an object to initialize a HasPtr * and must not delete that object; the HasPtr class will delete it */ class HasPtr { public: // HasPtr owns the pointer; pmust have been dynamically allocated HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { } // copy members and increment the use count HasPtr(const HasPtr &orig): ptr(orig.ptr), val(orig.val) { ++ptr->use; } HasPtr& operator=(const HasPtr&); // if use count goes to zero, delete the U_Ptr object ~HasPtr() { if (--ptr->use == 0) delete ptr; } private: U_Ptr *ptr; // points to use-counted U_Ptr class int val; };
接受一个指针和一个 int 值的 HasPtr 构造函数使用其指针形参创建一个新的 U_Ptr 对象。HasPtr 构造函数执行完毕后,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 基础对象。
赋值操作符比复制构造函数复杂一点:
HasPtr& HasPtr::operator=(const HasPtr &rhs) { ++rhs.ptr->use; // increment use count on rhs first if (--ptr->use == 0) delete ptr; // if use count goes to 0 on this object, delete it ptr = rhs.ptr; // copy the U_Ptr object val = rhs.val; // copy the int member return *this; }
在这里,首先将右操作数中的使用计数加 1,然后将左操作数对象的使用计数减 1 并检查这个使用计数。像析构函数中那样,如果这是指向 U_Ptr 对象的最后一个对象,就删除该对象,这会依次撤销 int 基础对象。将左操作数中的当前值减 1(可能撤销该对象)之后,再将指针从 rhs 复制到这个对象。赋值照常返回对这个对象的引用。这个赋值操作符在减少左操作数的使用计数之前使 rhs 的使用计数加 1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是 U_Ptr 基础对象的使用计数加 1 之后立即减 1。
现在需要改变访问 int* 的其他成员,以便通过 U_Ptr 指针间接获取 int:
class HasPtr { public: // copy control and constructors as before // accessors must change to fetch value from U_Ptr object int *get_ptr() const { return ptr->ip; } int get_int() const { return val; } // change the appropriate data member void set_ptr(int *p) { ptr->ip = p; } void set_int(int i) { val = i; } // return or change the value pointed to, so ok for const objects // Note: *ptr->ip is equivalent to *(ptr->ip) int get_ptr_val() const { return *ptr->ip; } void set_ptr_val(int i) { *ptr->ip = i; } private: U_Ptr *ptr; // points to use-counted U_Ptr class int val; };
获取和设置 int 成员的函数不变。那些使用指针操作的函数必须对 U_Ptr 解引用,以便获取 int* 基础对象。复制 HasPtr 对象时,int 成员的行为与第一个类中一样。所复制的是 int 成员的值,各成员是独立的,副本和原对象中的指针仍指向同一基础对象,对基础对象的改变将影响通过任一 HasPtr 对象所看到的值。然而,HasPtr 的用户无须担心悬垂指针。只要他们让 HasPtr 类负责释放对象,HasPtr 类将保证只要有指向基础对象的 HasPtr 对象存在,基础对象就存在。
- 建议:管理指针成员
具有指针成员的对象一般需要定义复制控制成员。如果依赖合成版本,会给类的用户增加负担。用户必须保证成员所指向的对象存在,只要还有对象指向该对象。为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数、赋值操作符和析构函数。这些成员可以定义指针成员的指针型行为或值型行为。值型类将指针成员所指基础值的副本给每个对象。复制构造函数分配新元素并从被复制对象处复制值,赋值操作符撤销所保存的原对象并从右操作数向左操作数复制值,析构函数撤销对象。
作为定义值型行为或指针型行为的另一选择,是使用称为“智能指针”的一些类。这些类在对象间共享同一基础值,从而提供了指针型行为。但它们使用复制控制技术以避免常规指针的一些缺陷。为了实现智能指针行为,类需要保证基础对象一直存在,直到最后一个副本消失。使用计数是管理智能指针类的通用技术。同一基础值的每个副本都有一个使用计数。复制构造函数将指针从旧对象复制到新对象时,会将使用计数加 1。赋值操作符将左操作数的使用计数减 1 并将右操作数的使用计数加 1,如果左操作数的使用计数减至 0,赋值操作符必须删除它所指向的对象,最后,赋值操作符将指针从右操作数复制到左操作数。析构函数将使用计数减 1,并且,如果使用计数减至 0,就删除基础对象。
- 定义值型类
处理指针成员的另一个完全不同的方法,是给指针成员提供值语义。具有值语义的类所定义的对象,其行为很像算术类型的对象:复制值型对象时,会得到一个不同的新副本。对副本所做的改变不会反映在原有对象上,反之亦然。string 类是值型类的一个例子。
要使指针成员表现得像一个值,复制 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 值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针。
我们看下面一小段代码:
int p = 8; cout << &p << endl; int *pp = &p; cout << pp << endl; int *ptr = new int(p);//ptr指向一个新生成的对象 cout << ptr << endl; cout << *ptr << endl;
输出结果:
说明int *ptr = new int(p)所生成的指针ptr会指向一个新的对象。因此,在复制构造函数的初始化成员列表中的ptr(new int(*orig.ptr)),使得ptr指向了新的对象,但是新对象的值又是和原对象中ptr指针指向的对象的值相同。//ptr和orig.ptr不仅它们俩的地址不同,而且它们俩的值也不同(指向的对象不同),但它们俩指向的对象的值是相同的。
此时我们再执行这段代码:
HasPtr ptr1(p, 8); HasPtr ptr2(ptr1);//此时ptr1和ptr2中的成员ptr不仅地址不同,自身的值也不同了(指向不同的对象)。 ptr2.set_ptr_val(9); cout << ptr1.get_ptr_val() << endl; cout << ptr2.get_ptr_val() << endl;
输出结果就不会相同了:
赋值操作符不需要分配新对象//因为赋值操作符的使用,说明这个对象已经被初始化过了,无论它是使用构造函数初始化还是使用复制构造函数初始化,都已经有了一个新的ptr成员(与orig.ptr指向不同对象的成员),它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:
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; }
换句话说,改变的是指针所指向的值,而不是指针。即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。