条款29: 避免返回内部数据的句柄

假设b是一个const string对象:

class string {
public:
  string(const char *value);        // 具体实现参见条款11
  ~string();                        // 构造函数的注解参见条款m5

  operator char *() const;          // 转换string -> char*;
                                    // 参见条款m5
  ...

private:
  char *data;
};

const string b("hello world");      // b是一个const对象

看看下面的情形:

char *str = b;               // 调用b.operator char*()

strcpy(str, "hi mom");       // 修改str指向的值

b的值现在还是"hello world"吗?或者,它是否已经变成了对母亲的问候语?答案完全取决于string::operator char*的实现。

下面是一个有欠考虑的实现,它导致了错误的结果。但是,它工作起来确实很高效,所以很多程序员才掉进它的错误陷阱之中:

// 一个执行很快但不正确的实现
inline string::operator char*() const
{ return data; }

这个函数的缺陷在于它返回了一个"句柄"(在本例中,是个指针),而这个句柄所指向的信息本来是应该隐藏在被调用函数所在的string对象的内部。这样,这个句柄就给了调用者自由访问data所指的私有数据的机会。换句话说,有了下面的语句:

char *str = b;

情况就会变成这样:

str------------------------->"hello world\0"

显然,任何对str所指向的内存的修改都使得b的有效值发生变化。所以,即使b声明为const,而且即使只是调用了b的某个const成员函数,b也会在程序运行过程中得到不同的值。特别是,如果str修改了它所指的值,b也会改变。

string::operator char*本身写的没有一点错,麻烦的是它可以用于const对象。如果这个函数不声明为const,就不会有问题,因为这样它就不能用于象b这样的const对象了。(const对象不能调用非const成员函数)

但是,将一个string对象转换成它相应的char*形式是很合理的一件事,无论这个对象是否为const。所以,还是应该使函数保持为const。这样的话,就得重写这个函数,使得它不返回指向对象内部数据的句柄:

// 一个执行慢但很安全的实现
inline string::operator char*() const
{
  char *copy = new char[strlen(data) + 1];
  strcpy(copy, data);

  return copy;

}

这个实现很安全,因为它返回的指针所指向的数据只是string对象所指向数据的拷贝;通过函数返回的指针无法修改string对象的值。当然,安全是要有代价的:这个版本的string::operator char* 运行起来比前面那个简单版本要慢;此外,函数的调用者还要记得delete掉返回的指针。

如果不能忍受这个版本的速度,或者担心内存泄露,可以来一点小小的改动:使函数返回一个指向const char的指针:

class string {
public:
  operator const char *() const;

  ...

};

inline string::operator const char*() const
{ return data; }

指针并不是返回内部数据句柄的唯一途径。引用也很容易被滥用。下面是一种常见的用法,还是拿string类做例子:

class string {
public:

  ...

  char& operator[](int index) const
  { return data[index]; }

private:
  char *data;
};

string s = "i‘m not constant";

s[0] = ‘x‘;               // 正确, s不是const

const string cs = "i‘m constant";

cs[0] = ‘x‘;              // 修改了const string,
                          // 但编译器不会通知

注意string::operator[]是通过引用返回结果的。这意味着函数的调用者得到的是内部数据data[index]的另一个名字,而这个名字可以用来修改const对象的内部数据。这个问题和前面看到的相同,只不过这次的罪魁祸首是引用,而不是指针。

这类问题的通用解决方案和前面关于指针的讨论一样:或者使函数为非const(const对象就不能调用该函数了),或者重写函数,使之不返回句柄(返回const引用即可)。如果想让string::operator[]既适用于const对象又适用于非const 对象,可以参见条款21。



并不是只有const成员函数需要担心返回句柄的问题,即使是非const成员函数也得承认:句柄的合法性失效的时间和它所对应的对象是完全相同的。这个时间可能比用户期望的要早很多,特别是当涉及的对象是由编译器产生的临时对象时。

例如,看看这个函数,它返回了一个string对象:

string somefamousauthor()           // 随机选择一个作家名
{                                   // 并返回之

  switch (rand() % 3) {             // rand()在<stdlib.h>中
                                    // (还有<cstdlib>。参见条款49)
  case 0:
    return "margaret mitchell";     // 此作家曾写了 "飘",
                                    // 一部绝对经典的作品
  case 1:
    return "stephen king";          // 他的小说使得许多人
                                    // 彻夜不眠
  case 2:
    return "scott meyers";          // 嗯...滥竽充数的一个
  }                                

  return "";                        // 程序不会执行到这儿,
                                    // 但对于一个有返回值的函数来说,
                                    // 任何执行途径上都要有返回值
}

somefamousauthor的返回值是一个string对象,一个临时string对象(参见条款m19)。这样的对象是暂时性的,它们的生命周期通常在函数调用表达式结束时终止。例如上面的情况中,包含somefamousauthor函数调用的表达式结束时,返回值对象的生命周期也就随之结束。

具体看看下面这个使用somefamousauthor的例子,假设string声明了一个上面的operator const char*成员函数:

const char *pc = somefamousauthor();

cout << pc;

不论你是否相信,谁也不能预测这段代码将会做些什么,至少不能确定它会做些什么。因为当你想打印pc所指的字符串时,字符串的值是不确定的。造成这一结果的原因在于pc初始化时发生了下面这些事件:
1.
产生一个临时string对象用以保存somefamousauthor的返回值。
2. 通过string的operator const
char*成员函数将临时string对象转换为const char*指针,并用这个指针初始化pc。
3.
临时string对象被销毁,其析构函数被调用。析构函数中,data指针被删除(代码详见条款11)。然而,data和pc所指的是同一块内存,所以现在pc指向的是被删除的内存--------其内容是不可确定的。

条款29: 避免返回内部数据的句柄

时间: 2024-11-05 16:23:16

条款29: 避免返回内部数据的句柄的相关文章

Effective C++ Item 28 避免返回对象内部数据的引用或指针

本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie Item 31 经验:避免返回handles(包含 references.指针.迭代器)指向对象内部.遵守这个条款可添加封装性, 帮助 const 成员函数的行为像个 const,并将发生"虚吊号码牌"(dangling handles)的可能性降至最低. 演示样例: class Point{ public: Point(int x, int y); //... void set

Effective C++ 条款28 避免返回handles指向对象内部成分

1. 所谓的handles指的是引用,指针,迭代器(可能与windows的句柄有所区别),返回一个handles会导致提供给用户对象内部数据的间接访问,这降低了成员变量的封装性,例如: class Demo{ public: ... int* getPtr() const { return ptr; } private: ... int* ptr; } 这段代码语法正确,但是它违背了将getPtr设为const的初衷:从语法上来说ptr所指对象也属于Demo对象的一部分,但从语法上讲,由于编译器

【Struts2】SSH如何返回JSON数据

  在开发中我们经常遇到客户端和后台数据的交互,使用比较多的就是json格式了.在这里以简单的Demo总结两种ssh返回Json格式的数据 项目目录如下 主要是看 上图选择的部分 WebRoot里面就是平常的配置 第一种方法是使用com.google.gson.Gson 将对象转化为Json字符串  (gson-1.6.jar) 主要的代码如下 1 package com.javen.tool; 2 3 import java.io.IOException; 4 import java.io.P

struts返回json数据

想要在struts中返回json格式数据有两种办法. 1.使用servlet的输出流 实际上就是在struts中获取response对象的输出流.然后写入你要返回的json数据,本质和用servlet返回json数据是一样的,需要自己导入json的jar包.不做详细介绍. 2.试用struts对json的扩展 这里需要两个jar包,xwork-core-2.1.6.jar和struts2-json-plugin-2.1.8.jar.如果是用MyEclipse注入的struts环境就不需要手动了.

PHP-------ajax返回值 返回JSON 数据

ajax返回值  返回JSON  数据 ajax返回值 有text   JSON ajax返回值  返回JSON  数据 1 <title>无标题文档</title> 2 <script src="../jquery-1.11.2.min.js"> 3 </script> 4 5 <!--ajax返回值 有text JSON--> 6 <!--ajax返回值 返回JSON 数据--> 7 8 9 10 11 <

ADO.NET快速入门——利用Command对象的ExecuteScalar()方法返回一个数据值

相关知识: 有些SQL操作,例如SUM,只会从数据库返回一个数据值,而不是多行数据 尽管也可以使用ExecuteReader()返回一个DataReader对象,代表该数据值,但是使用Command对象的ExecuteScalar方法更加方便 ExecuteScalar()方法:该方法只能执行SELECT语句,通常用于统计,例如返回符合条件的记录个数 代码示例: 1 using System; 2 using System.Collections.Generic; 3 using System.

php返回json数据函数实例

本文实例讲述了php返回json数据函数的用法,分享给大家供大家参考.具体方法如下: json_encode()函数用法: ? 1 echo json_encode(array('a'=>'bbbb','c'=>'ddddd'); 这样就会生成一个标准的json格式的数据 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php //需要执行的S

jQuery调用WebService返回JSON数据

相信大家都比较了解JSON格式的数据对于ajax的方便,不了解的可以从网上找一下这方面的资料来看一下,这里就不多说了,不清楚的可以在网上查一下,这里只说一下因为参数设置不当引起的取不到返回值的问题. 在用jQuery调用WebService的时候,它contentType默认为 以下是WebService服务端的代码: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.W

【04】AJAX接收服务器返回的数据

AJAX接收服务器返回的数据 readyState 和 status 属性 readyState 属性保存有 XMLHttpRequest 对象的交互状态,从 0 到 4 变化: 0 :未初始化(还没有调用send()方法): 1:载入(已调用send()方法,正在发送请求): 2:载入完成(send()方法执行完成,已经接收到全部响应数据): 3:交互(正在解析响应数据): 4:完成(响应数据解析完成,可以在客户端调用了). status 属性保存有 XMLHttpRequest 对象与后台交