设为首页 加入收藏

TOP

4.4.3 返回引用
2013-10-07 15:04:00 来源: 作者: 【 】 浏览:63
Tags:4.4.3 返回 引用

4.4.3  返回引用

一个广受提倡的用于减少运行时间的技术就是返回引用,而不是返回值,因为返回值将需要高昂的拷贝开销。考虑4.2.2节的List::head函数,所有head函数的调用者-包括那些只是检查表头节点值的用户-都要承受把节点值拷贝出去的开销:

  1. List<Widget> l;  
  2. //...  
  3. cout << "first widget is " << l.head();    //拷贝了一个Widget.  

为了避免这样使用的拷贝开销,我们可以修改head函数,让它返回一个指向内部存储表头值的拷贝的引用:
  1. template<class T> 
  2. class List {  
  3. public:  
  4.      const T& head() const;    //返回一个引用  
  5.      //...  
  6. };  

如果List类如上面一样定义,那么用户代码将不会拷贝一个Widget对象。我们还在声明head函数时,让它返回一个const引用,因为返回一个非const引用将允许用户在不使用cast转型去除const关键字的情况下就可以改变const List中存储的值:
  1. void whoops(const List<Widget>&l) {  
  2.     widget newvalue;  
  3.     //...  
  4.     1.head() = newvalue;      //uh-oh!如果head函数返回一个非const  
  5.                                    //对象,这条语句是合法的,但不是一个好的  
  6.                                    //主意  
  7.     //...  
  8. }  

对于任何返回引用的程序库函数,程序库文档都应该给出关于引用存活期(指引用的有效时间)的说明。例如,我们如下给出head函数的文档:

head函数返回的引用将保持有效,直到引用指向的值从所给的List中删除。

返回引用有两个缺点。首先,它使用户的代码更加容易产生错误。回想一下4.2.2节的insert函数和tail函数:insert函数把一个节点插入到List的表头,tail函数返回除了头节点之外的表尾,现在让我们考虑下面代码:

  1. List<int> l;  
  2. l.insert(0);  
  3. const int& i = l.head();  
  4. l.insert(1);         //i仍然是有效的。  
  5. ll = l.tail();      //i仍然是有效的。  
  6. l = 1.tail();     //i现在变成无效了。  

当i变成无效之后,任何试图使用引用i的操作都会是未定义的操作。但在返回一个T类型(即返回值),而不是返回T&类型(即返回引用)的head函数里,就不会出现上面这种未定义的操作。

返回引用的第二个缺点是限制了我们实现所给类的方式。例如,我们发现,如果以共享的方式实现类List,那么大多数用户的代码运行速度将会更快。特别地,让我们考虑List<int>x,它的底层实现如下图所示:

 
如果用户编写下面代码:
  1. List<int> y = x; 

那么y将和x共享同一个节点:
 
如果用户在x中插入一个7,并在y中插入一个8,那么底层实现将会如下所示:
 
最后,如果用户要在y指向的链表尾部追加值9,那么x和y将不再共享同一个表尾;因此,我们把所有共享节点都拷贝出来生成一个新的链表,并把9附加到生成的新链表的尾部:
 
现在假设函数remove_all将会移除链表中所有的值,函数append将会在链表来尾追加一个给定的值。考虑下面的代码:
  1. List<int> x;  
  2. x.insert(0);  
  3. List<int> y = x;  
  4. const int& i = y.head():  

下面是我们上面代码得到的底层实现:
 
如果我们把1追加到y的末尾:
  1. y.append(1);        //i仍然是有效的。 

那么x和y将不再具有相同的表尾,所以y的List将是一个拷贝的List。(我们也可以直接在y原来的List追加一个节点,即让y保持原来的链表,而让x指向一个新的拷贝。但这个方法并不能解决问题,而只是把问题转移了-假设有一个指向x.head()函数的引用,那样问题将会出现。)现在的底层实现如下:

 
假设我们现在改变x:

  1. x.remove_all();       //i是否依然有效呢? 

因为程序中并没有其他和x共享上面节点的List对象,所以remove_all操作将会删除x中所有的节点。从图形来看,我们将得到以下结果:

 

在调用了remove_all函数之后,引用i可能会是无效的。因此,基于上面List类的实现,我们根本不能保证(除非我们特别聪明),head函数返回的引用将会保持有效,直到此引用指向的值从所给的List(即我们调用head函数的List)中删除。

基于上面List的实现,我们对head函数唯一可以做的保证只能如下:

head函数返回的引用将保持有效,直到对任何List<T>执行了非const操作。

遗憾的是,有了上面这个保证,用户程序却会有意无意地操纵无效的引用。所以说,为了加强上面这个保证,来防止用户程序操纵无效的引用,我们往往需要改变List的实现,而这种改变通常都会降低List的效率。

返回引用可以提高效率,但它可能会使用户代码更加容易产生错误,并且限制类的实现方式。如果函数确实需要返回引用,那么程序库就必须给出关于每个引用存活期的文档。所以说,使用引用的过程必须是非常小心谨慎的,来确保每个引用都是有效的、正确的。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇4.5.3 静态对象 下一篇4.4.2 虚函数

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: