一、概述
大家都用过代理服务器,代理服务器是从出发点到目的地之间的中间层。而Proxy模式中的Proxy功能上与此类似,是对象的访问者与对象之间的中间层。
Proxy(代理)模式可用于解决在直接访问对象不方便或不符合要求时,为这个对象提供一种代理,以控制对该对象的访问。
二、结构
Proxy模式的类图结构如下图所示:
图 1:Proxy模式类图示意
在上面的类图中,Proxy类是Subject类的子类,但个人认为,单纯从Proxy的意图上讲,这一约束不是必须的。
与前面讨论的Decorator模式不同,在应用Proxy模式时,我们可能知道目标,也可能不知道目标(此时,被代理的对象由Proxy类创建),而Decorator模式下我们往往按照访问目标的方式去访问Decorator,即我们总是知道目标,或者说我们更注意的是目标,而不是Decorator。并且,我们Proxy类可能提供与被访问者不同的接口,而Decorator模式下应保证接口的一致性,以便用户可以用与访问Decoratee一样的方式来访问Decorator。
单纯从结构上讲,Adapter模式与Proxy模式比较相似,但二者的区别在于意图的不同:Adapter的意图在于接口的转换,而Proxy的意图在于代理(或控制),因此,有人形象地将Proxy模式称为“票贩子模式”,而将Adapter模式称为“外汇买卖模式”。
三、应用
Proxy这个模式的目的比较笼统,引入Proxy的目的是在Subject和Client之间构建一个中间层,它适用的情况很多(以至于JDK1 .3开始,特别添加了对Proxy的支持,详见java .lang .reflect .Proxy说明文档),可以简单分成以下几类:
1、远程访问代理(可能为了简化客户代码,也可能为了集中管理等);
2、重要对象(可能是共享对象或大的,耗资源的对象)访问代理;
3、访问控制代理。
在实际应用中,可能出现同时属于以上多种类别的情况。下面是一些可以应用Proxy模式的常见情况:
1、远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中,远程代理又叫做大使(Ambassador)。常见的应用如CORBA、DCOM等。
2、虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。如某个Word文档中包含很多较大的图片,需要花费很长时间才能显示出来,那么使用编辑器或浏览器打开这个文档,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片,先在图片的位置显示一个框,然后随着图片逐步被打开,再对显示区域以及内容进行调整,直至显示所需内容。再比如说数据库的显示,客户现在只需要显示 1 - 10条,你放在内存中 10000条对客户没有任何意义,这时可以用一个代理,只是取出前 10条就可以了,当客户选择显示下 10条时再取出接下来的 10条数据显示给客户。
3、Copy -on -Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。这在访问大的对象和防止频繁拷贝造成过多消耗时经常被用到,现代操作系统往往使用这种技术来防止频繁写磁盘,对于我们的普通应用而言,这种技术也被经常使用,当对象未发生修改时,先使用已有的对象,任何一方发生修改时才执行拷贝动作,创建新的对象,以避免在创建对象上过多的消耗(因为我们有可能在整个对象存在期间不会被修改),从而提高处理效率。
4、保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。对于硬件等系统资源而言,OS即是一层保护代理。
5、Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
6、防火墙(Firewall)代理:保护目标,不让恶意用户接近。
7、同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
8、智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
总之,当需要对Client访问目标对象的行为进行控制时,请尽可能不要通过继承来对目标进行深入规范,而是采用Proxy模式在Client和Subject之间建立一个中间层,这将为我们的系统提供更大的可扩展性。
四、优缺点
五、举例
由于Proxy模式的目的是在Subject和Client之间构建一个中间层,以控制Client对Subject的访问,其应用十分广泛,很难举出一个面面俱到的例子,以下用两个典型应用来简单演示Proxy模式的应用。如果你感兴趣,也可以研究一下STL的auto_ptr和string的实现,其中也用到了Proxy模式,其中,std ::string使用了Copy -on -Write技术。
下面是一个采用Proxy模式进行图片加载的例子(Virtual Proxy , Java Code):
注:运行该示例前请在程序运行目录下放置一个图片文件,并将其命名为test .jpg
import java .awt .*;
import java .awt .event .*;
import javax .swing .*;
import javax .swing .border .*;
// This class tests a virtual proxy, which is a proxy that
// delays loading an expensive resource (an icon) until that
// resource is needed.
public class VirtualProxyTest extends JFrame {
private static String IMAGE_NAME = "test.jpg" ;
private static int IMAGE_X = 256 , IMAGE_Y = 256 ;
private JPanel normPanel , proxyPanel ;
public VirtualProxyTest () {
super ( "Virtual Proxy Test" );
normPanel = new NormPanel (IMAGE_NAME );
proxyPanel = new ImageProxy (IMAGE_NAME );
normPanel .setPreferredSize ( new Dimension (IMAGE_X , IMAGE_Y ));
proxyPanel .setPreferredSize ( new Dimension (IMAGE_X , IMAGE_Y ));
Container c = getContentPane ();
c .setLayout ( new GridLayout ( 1 , 2 ));
c .add (normPanel );
c .add (proxyPanel );
// Set the bounds of the frame, and the frame's default
// close operation.
setDefaultCloseOperation (JFrame .EXIT_ON_CLOSE );
}
static public void main (String args []) {
VirtualProxyTest app = new VirtualProxyTest ();
app .show ();
app .pack ();
}
}
class NormPanel extends JPanel {
private String imageName = null ;
private Image img = null ;
public NormPanel (String imageName ) {
this .imageName = imageName ;
img = Toolkit .getDefaultToolkit ().getImage (imageName );
}
public void paint (Graphics g ) {
g .drawImage (img , 0 , 0 , getWidth (), getHeight (), this ); //draw image
}
}
// ImageProxy is a proxy (or surrogate) for an image.
// The proxy delays loading the image until the first time the
// image is drawn. While the icon is loading its image, the
// proxy draws a border and the message "Loading image..."
class ImageProxy extends JPanel implements Runnable {
boolean isIconCreated = false ;
private String imageName = null ;
private Image img = null ;
Thread imageLoadThread = null ;
public ImageProxy (String imageName ) {
this .imageName = imageName ;
imageLoadThread = new Thread ( this );
imageLoadThread .start ();
}
// The proxy's paint() method is overloaded to draw a border
// and a message ("Loading image...") while the image
// loads. After the image has loaded, it is drawn. Notice
// that the proxy does not load the image until it is
// actually needed.
public void paint (Graphics g ) {
if (isIconCreated ) {
g .drawImage (img , 0 , 0 , getWidth (), getHeight (), this ); //draw image
}
else {
g .drawRect ( 1 , 1 , getWidth () - 2 , getHeight () - 2 );
g .drawString ( "Loading image..." , 20 , 20 );
}
}
public void run () {
try {
// Slow down the image-loading process.
Thread .currentThread ().sleep ( 2000 );
// create the image
img = Toolkit .getDefaultToolkit ().getImage (imageName );
isIconCreated = true ;
} catch (InterruptedException ex ) {
ex .printStackTrace ();
}
repaint ();
}
}
以下示例取自Thinking in C ++ 2nd,演示了Copy -on -Write技术的实现方法,耐心的Bruce Eckel用软件工程领域经典的Dog &DogHouse的例子清晰地告诉了我们应该如何实现Copy -on -Write(除了两处注释外,所有代码出自Bruce Eckel)。此外,关于string类中如何运用Copy -on -Write的问题,见参考 1:
//: C12:ReferenceCounting.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Reference count, copy-on-write
#include <string>
#include <iostream>
using namespace std ;
// assistant function
inline void require ( bool requirement ,
const std ::string & msg = "Requirement failed" ){
using namespace std ;
if (!requirement ) {
fputs (msg .c_str (), stderr );
fputs ( "/n" , stderr );
exit ( 1 );
}
}
// Subject class
class Dog {
string nm ;
int refcount ;
Dog ( const string & name )
: nm (name ), refcount ( 1 ) {
cout << "Creating Dog: " << * this << endl ;
}
// Prevent assignment:
Dog & operator =( const Dog & rv );
public :
// Dogs can only be created on the heap:
static Dog * make ( const string & name ) {
return new Dog (name );
}
Dog ( const Dog & d )
: nm (d .nm + " copy" ), refcount ( 1 ) {
cout << "Dog copy-constructor: "
<< * this << endl ;
}
~Dog () {
cout << "Deleting Dog: " << * this << endl ;
}
void attach () {
++refcount ;
cout << "Attached Dog: " << * this << endl ;
}
void detach () {
require (refcount != 0 );
cout << "Detaching Dog: " << * this << endl ;
// Destroy object if no one is using it:
if (--refcount == 0 ) delete this ;
}
// Conditionally copy this Dog.
// Call before modifying the Dog, assign
// resulting pointer to your Dog*.
Dog * unalias () {
cout << "Unaliasing Dog: " << * this << endl ;
// Don't duplicate if not aliased:
if (refcount == 1 ) return this ;
--refcount ;
// Use copy-constructor to duplicate:
return new Dog (* this );
}
void rename ( const string & newName ) {
nm = newName ;
cout << "Dog renamed to: " << * this << endl ;
}
friend ostream &
operator <<(ostream & os , const Dog & d ) {
return os << "[" << d .nm << "], rc = "
<< d .refcount ;
}
};
// Proxy class
class DogHouse {
Dog * p ;
string houseName ;
public :
DogHouse (Dog * dog , const string & house )
: p (dog ), houseName (house ) {
cout << "Created DogHouse: " << * this << endl ;
}
DogHouse ( const DogHouse & dh )
: p (dh .p ),
houseName ( "copy-constructed " +
dh .houseName ) {
p ->attach ();
cout << "DogHouse copy-constructor: "
<< * this << endl ;
}
DogHouse & operator =( const DogHouse & dh ) {
// Check for self-assignment:
if (&dh != this ) {
houseName = dh .houseName + " assigned" ;
// Clean up what you're using first:
p ->detach ();
p = dh .p ; // Like copy-constructor
p ->attach ();
}
cout << "DogHouse operator= : "
<< * this << endl ;
return * this ;
}
// Decrement refcount, conditionally destroy
~DogHouse () {
cout << "DogHouse destructor: "
<< * this << endl ;
p ->detach ();
}
void renameHouse ( const string & newName ) {
houseName = newName ;
}
void unalias () { p = p ->unalias (); }
// Copy-on-write. Anytime you modify the
// contents of the pointer you must
// first unalias it:
void renameDog ( const string & newName ) {
unalias ();
p ->rename (newName );
}
// ... or when you allow someone else access:
Dog * getDog () {
unalias ();
return p ;
}
friend ostream &
operator <<(ostream & os , const DogHouse & dh ) {
return os << "[" << dh .houseName
<< "] contains " << *dh .p ;
}
};
int main () {
DogHouse
fidos (Dog ::make ( "Fido" ), "FidoHouse" ),
spots (Dog ::make ( "Spot" ), "SpotHouse" );
cout << "Entering copy-construction" << endl ;
DogHouse bobs (fidos );
cout << "After copy-constructing bobs" << endl ;
cout << "fidos:" << fidos << endl ;
cout << "spots:" << spots << endl ;
cout << "bobs:" << bobs << endl ;
cout << "Entering spots = fidos" << endl ;
spots = fidos ;
cout << "After spots = fidos" << endl ;
cout << "spots:" << spots << endl ;
cout << "Entering self-assignment" << endl ;
bobs = bobs ;
cout << "After self-assignment" << endl ;
cout << "bobs:" << bobs << endl ;
// Comment out the following lines:
cout << "Entering rename(/"Bob/")" << endl ;
bobs .getDog ()->rename ( "Bob" );
cout << "After rename(/"Bob/")" << endl ;
return 0 ;
} ///:~
参考:
1、More Effective C ++ Item M29:引用计数
大家都用过代理服务器,代理服务器是从出发点到目的地之间的中间层。而Proxy模式中的Proxy功能上与此类似,是对象的访问者与对象之间的中间层。
Proxy(代理)模式可用于解决在直接访问对象不方便或不符合要求时,为这个对象提供一种代理,以控制对该对象的访问。
二、结构
Proxy模式的类图结构如下图所示:
![](/images/blog_csdn_net/billdavid/136320/o_Proxy.jpg)
图 1:Proxy模式类图示意
在上面的类图中,Proxy类是Subject类的子类,但个人认为,单纯从Proxy的意图上讲,这一约束不是必须的。
与前面讨论的Decorator模式不同,在应用Proxy模式时,我们可能知道目标,也可能不知道目标(此时,被代理的对象由Proxy类创建),而Decorator模式下我们往往按照访问目标的方式去访问Decorator,即我们总是知道目标,或者说我们更注意的是目标,而不是Decorator。并且,我们Proxy类可能提供与被访问者不同的接口,而Decorator模式下应保证接口的一致性,以便用户可以用与访问Decoratee一样的方式来访问Decorator。
单纯从结构上讲,Adapter模式与Proxy模式比较相似,但二者的区别在于意图的不同:Adapter的意图在于接口的转换,而Proxy的意图在于代理(或控制),因此,有人形象地将Proxy模式称为“票贩子模式”,而将Adapter模式称为“外汇买卖模式”。
三、应用
Proxy这个模式的目的比较笼统,引入Proxy的目的是在Subject和Client之间构建一个中间层,它适用的情况很多(以至于JDK1 .3开始,特别添加了对Proxy的支持,详见java .lang .reflect .Proxy说明文档),可以简单分成以下几类:
1、远程访问代理(可能为了简化客户代码,也可能为了集中管理等);
2、重要对象(可能是共享对象或大的,耗资源的对象)访问代理;
3、访问控制代理。
在实际应用中,可能出现同时属于以上多种类别的情况。下面是一些可以应用Proxy模式的常见情况:
1、远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中,远程代理又叫做大使(Ambassador)。常见的应用如CORBA、DCOM等。
2、虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。如某个Word文档中包含很多较大的图片,需要花费很长时间才能显示出来,那么使用编辑器或浏览器打开这个文档,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片,先在图片的位置显示一个框,然后随着图片逐步被打开,再对显示区域以及内容进行调整,直至显示所需内容。再比如说数据库的显示,客户现在只需要显示 1 - 10条,你放在内存中 10000条对客户没有任何意义,这时可以用一个代理,只是取出前 10条就可以了,当客户选择显示下 10条时再取出接下来的 10条数据显示给客户。
3、Copy -on -Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。这在访问大的对象和防止频繁拷贝造成过多消耗时经常被用到,现代操作系统往往使用这种技术来防止频繁写磁盘,对于我们的普通应用而言,这种技术也被经常使用,当对象未发生修改时,先使用已有的对象,任何一方发生修改时才执行拷贝动作,创建新的对象,以避免在创建对象上过多的消耗(因为我们有可能在整个对象存在期间不会被修改),从而提高处理效率。
4、保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。对于硬件等系统资源而言,OS即是一层保护代理。
5、Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
6、防火墙(Firewall)代理:保护目标,不让恶意用户接近。
7、同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
8、智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
总之,当需要对Client访问目标对象的行为进行控制时,请尽可能不要通过继承来对目标进行深入规范,而是采用Proxy模式在Client和Subject之间建立一个中间层,这将为我们的系统提供更大的可扩展性。
四、优缺点
五、举例
由于Proxy模式的目的是在Subject和Client之间构建一个中间层,以控制Client对Subject的访问,其应用十分广泛,很难举出一个面面俱到的例子,以下用两个典型应用来简单演示Proxy模式的应用。如果你感兴趣,也可以研究一下STL的auto_ptr和string的实现,其中也用到了Proxy模式,其中,std ::string使用了Copy -on -Write技术。
下面是一个采用Proxy模式进行图片加载的例子(Virtual Proxy , Java Code):
注:运行该示例前请在程序运行目录下放置一个图片文件,并将其命名为test .jpg
import java .awt .*;
import java .awt .event .*;
import javax .swing .*;
import javax .swing .border .*;
// This class tests a virtual proxy, which is a proxy that
// delays loading an expensive resource (an icon) until that
// resource is needed.
public class VirtualProxyTest extends JFrame {
private static String IMAGE_NAME = "test.jpg" ;
private static int IMAGE_X = 256 , IMAGE_Y = 256 ;
private JPanel normPanel , proxyPanel ;
public VirtualProxyTest () {
super ( "Virtual Proxy Test" );
normPanel = new NormPanel (IMAGE_NAME );
proxyPanel = new ImageProxy (IMAGE_NAME );
normPanel .setPreferredSize ( new Dimension (IMAGE_X , IMAGE_Y ));
proxyPanel .setPreferredSize ( new Dimension (IMAGE_X , IMAGE_Y ));
Container c = getContentPane ();
c .setLayout ( new GridLayout ( 1 , 2 ));
c .add (normPanel );
c .add (proxyPanel );
// Set the bounds of the frame, and the frame's default
// close operation.
setDefaultCloseOperation (JFrame .EXIT_ON_CLOSE );
}
static public void main (String args []) {
VirtualProxyTest app = new VirtualProxyTest ();
app .show ();
app .pack ();
}
}
class NormPanel extends JPanel {
private String imageName = null ;
private Image img = null ;
public NormPanel (String imageName ) {
this .imageName = imageName ;
img = Toolkit .getDefaultToolkit ().getImage (imageName );
}
public void paint (Graphics g ) {
g .drawImage (img , 0 , 0 , getWidth (), getHeight (), this ); //draw image
}
}
// ImageProxy is a proxy (or surrogate) for an image.
// The proxy delays loading the image until the first time the
// image is drawn. While the icon is loading its image, the
// proxy draws a border and the message "Loading image..."
class ImageProxy extends JPanel implements Runnable {
boolean isIconCreated = false ;
private String imageName = null ;
private Image img = null ;
Thread imageLoadThread = null ;
public ImageProxy (String imageName ) {
this .imageName = imageName ;
imageLoadThread = new Thread ( this );
imageLoadThread .start ();
}
// The proxy's paint() method is overloaded to draw a border
// and a message ("Loading image...") while the image
// loads. After the image has loaded, it is drawn. Notice
// that the proxy does not load the image until it is
// actually needed.
public void paint (Graphics g ) {
if (isIconCreated ) {
g .drawImage (img , 0 , 0 , getWidth (), getHeight (), this ); //draw image
}
else {
g .drawRect ( 1 , 1 , getWidth () - 2 , getHeight () - 2 );
g .drawString ( "Loading image..." , 20 , 20 );
}
}
public void run () {
try {
// Slow down the image-loading process.
Thread .currentThread ().sleep ( 2000 );
// create the image
img = Toolkit .getDefaultToolkit ().getImage (imageName );
isIconCreated = true ;
} catch (InterruptedException ex ) {
ex .printStackTrace ();
}
repaint ();
}
}
以下示例取自Thinking in C ++ 2nd,演示了Copy -on -Write技术的实现方法,耐心的Bruce Eckel用软件工程领域经典的Dog &DogHouse的例子清晰地告诉了我们应该如何实现Copy -on -Write(除了两处注释外,所有代码出自Bruce Eckel)。此外,关于string类中如何运用Copy -on -Write的问题,见参考 1:
//: C12:ReferenceCounting.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Reference count, copy-on-write
#include <string>
#include <iostream>
using namespace std ;
// assistant function
inline void require ( bool requirement ,
const std ::string & msg = "Requirement failed" ){
using namespace std ;
if (!requirement ) {
fputs (msg .c_str (), stderr );
fputs ( "/n" , stderr );
exit ( 1 );
}
}
// Subject class
class Dog {
string nm ;
int refcount ;
Dog ( const string & name )
: nm (name ), refcount ( 1 ) {
cout << "Creating Dog: " << * this << endl ;
}
// Prevent assignment:
Dog & operator =( const Dog & rv );
public :
// Dogs can only be created on the heap:
static Dog * make ( const string & name ) {
return new Dog (name );
}
Dog ( const Dog & d )
: nm (d .nm + " copy" ), refcount ( 1 ) {
cout << "Dog copy-constructor: "
<< * this << endl ;
}
~Dog () {
cout << "Deleting Dog: " << * this << endl ;
}
void attach () {
++refcount ;
cout << "Attached Dog: " << * this << endl ;
}
void detach () {
require (refcount != 0 );
cout << "Detaching Dog: " << * this << endl ;
// Destroy object if no one is using it:
if (--refcount == 0 ) delete this ;
}
// Conditionally copy this Dog.
// Call before modifying the Dog, assign
// resulting pointer to your Dog*.
Dog * unalias () {
cout << "Unaliasing Dog: " << * this << endl ;
// Don't duplicate if not aliased:
if (refcount == 1 ) return this ;
--refcount ;
// Use copy-constructor to duplicate:
return new Dog (* this );
}
void rename ( const string & newName ) {
nm = newName ;
cout << "Dog renamed to: " << * this << endl ;
}
friend ostream &
operator <<(ostream & os , const Dog & d ) {
return os << "[" << d .nm << "], rc = "
<< d .refcount ;
}
};
// Proxy class
class DogHouse {
Dog * p ;
string houseName ;
public :
DogHouse (Dog * dog , const string & house )
: p (dog ), houseName (house ) {
cout << "Created DogHouse: " << * this << endl ;
}
DogHouse ( const DogHouse & dh )
: p (dh .p ),
houseName ( "copy-constructed " +
dh .houseName ) {
p ->attach ();
cout << "DogHouse copy-constructor: "
<< * this << endl ;
}
DogHouse & operator =( const DogHouse & dh ) {
// Check for self-assignment:
if (&dh != this ) {
houseName = dh .houseName + " assigned" ;
// Clean up what you're using first:
p ->detach ();
p = dh .p ; // Like copy-constructor
p ->attach ();
}
cout << "DogHouse operator= : "
<< * this << endl ;
return * this ;
}
// Decrement refcount, conditionally destroy
~DogHouse () {
cout << "DogHouse destructor: "
<< * this << endl ;
p ->detach ();
}
void renameHouse ( const string & newName ) {
houseName = newName ;
}
void unalias () { p = p ->unalias (); }
// Copy-on-write. Anytime you modify the
// contents of the pointer you must
// first unalias it:
void renameDog ( const string & newName ) {
unalias ();
p ->rename (newName );
}
// ... or when you allow someone else access:
Dog * getDog () {
unalias ();
return p ;
}
friend ostream &
operator <<(ostream & os , const DogHouse & dh ) {
return os << "[" << dh .houseName
<< "] contains " << *dh .p ;
}
};
int main () {
DogHouse
fidos (Dog ::make ( "Fido" ), "FidoHouse" ),
spots (Dog ::make ( "Spot" ), "SpotHouse" );
cout << "Entering copy-construction" << endl ;
DogHouse bobs (fidos );
cout << "After copy-constructing bobs" << endl ;
cout << "fidos:" << fidos << endl ;
cout << "spots:" << spots << endl ;
cout << "bobs:" << bobs << endl ;
cout << "Entering spots = fidos" << endl ;
spots = fidos ;
cout << "After spots = fidos" << endl ;
cout << "spots:" << spots << endl ;
cout << "Entering self-assignment" << endl ;
bobs = bobs ;
cout << "After self-assignment" << endl ;
cout << "bobs:" << bobs << endl ;
// Comment out the following lines:
cout << "Entering rename(/"Bob/")" << endl ;
bobs .getDog ()->rename ( "Bob" );
cout << "After rename(/"Bob/")" << endl ;
return 0 ;
} ///:~
参考:
1、More Effective C ++ Item M29:引用计数