不要放弃你的内部? [C ++]

时间:2009-12-17 19:40:44

标签: c++ return-value

我正在阅读一本名为“C ++编码标准”的书作者:Herb Sutter,Andrei Alexandrescu和本书第42章就是一个例子:(章节很短,所以我正在冒昧并粘贴它的一部分)

考虑:

 class Socket {
 public:
   // … constructor that opens handle_, destructor that closes handle_, etc. …
   int GetHandle() const {return handle_;} // avoid this - (1) <-why this is bad code?
                                           // and why there is a comment to avoid such code??
 private:
   int handle_; // perhaps an OS resource handle
 };
  

数据隐藏是一种功能强大的抽象和模块化设备(参见第11和41项)。但是隐藏数据然后给它提供句柄是弄巧成拙的,就像锁住房子并将钥匙留在锁中一样。这是因为:

     

客户端现在有两种实现功能的方法:它们可以使用类的抽象(Socket)或直接操作类所依赖的实现(套接字的C风格句柄)。在后一种情况下,对象不知道它认为拥有的资源发生了重大变化。现在,类无法可靠地丰富或修饰功能(例如,代理,记录,收集统计信息),因为客户端可以绕过装饰的,受控制的实现以及它认为正在添加的任何不变量,这使得接下来不可能进行正确的错误处理(参见条款70)

     

该类无法更改其抽象的底层实现,因为客户端依赖它:如果稍后升级Socket以支持具有不同低级基元集的不同协议,则调用获取底层句柄的代码并错误地操作它将被默默地打破。

     

该类不能强制执行其不变量,因为调用代码可以改变类不知道的状态:例如,有人可以关闭Socket对象使用的句柄而不通过Socket成员函数,从而使对象无效。

     

客户端代码可以存储您的类返回的句柄,并在您的类的代码使它们失效后尝试使用它们。

这是本书的摘要:

  

不要过多自愿:避免将句柄返回到由您的班级管理的内部数据,这样客户就不会无法控制地修改您的对象认为拥有的状态。

基本上我要求的是:

  1. 为什么我标记为(1)的行被列为坏代码的示例(我一直认为返回指针或引用是一个坏主意,但按值返回是正常的。这里他们说返回按价值也是个坏主意?)

  2. 是否有可能存在'&amp;'缺少和他们真正的意思是不通过引用或指针返回内部数据?

  3. 谢谢。

10 个答案:

答案 0 :(得分:32)

我认为你所缺少的是一个句柄 - 即使它在类型系统中由int表示 - 对某事物的引用。这不会返回一些信息值 - 它将对象的内部引用返回给系统资源。该类应该自己管理这个句柄,并且只有通过类接口才能使外部世界影响句柄。

答案 1 :(得分:9)

问题不在于低级细节(代码是非常好的C ++)。

问题是你正在破坏你的抽象。如果在将来,您需要将其更改为某种指针,而不是使用int句柄。如果不破坏任何使用您班级的客户,您将无法进行更改。

答案 2 :(得分:4)

关键不在于您按值返回,这很好,关键是您要返回资源句柄。

相反,您的类应该组织访问该资源的方法,并提供围绕该资源的IO。

例如,如果资源是文件,那么您的类应该具有write()和read()方法,该方法可以读取文件或从文件写入。

答案 3 :(得分:4)

请注意handle_的声明:

  int handle_; // perhaps an OS resource handle

即使你从C ++的角度返回一个int值,从操作系统的角度来看,句柄是某个OS资源的“引用”。

答案 4 :(得分:3)

1)他们正在谈论将句柄返回到套接字。在许多情况下,和int都可以(比如数组的大小或其他东西),但在这种情况下,int可用于调用低级C函数,这些函数将修改套接字而不需要您的类知识。正如章节所述,任何允许你的类的底层表示在不知情的情况下改变的东西都是糟糕的设计。

2)我怀疑他们错过了裁判,原因如上所述

答案 5 :(得分:1)

当你说返回引用和指向私有成员的指针是不好的做法时你是对的,但是这里返回值也是不好的,因为该值具有内部意义。句柄可以被认为是对象的地址,是操作系统内部深层管理的对象的地址。

允许外部类访问此句柄将是个坏消息。想象一下它是一个文件句柄。外部类现在可以关闭你的文件(知道它的句柄),你的包装类对它一无所知。你班上的内部现在处于无效状态。

答案 6 :(得分:1)

代码应该显示谁使用该类不必要的信息,它应该使类接口更加独立于实际实现。
需要资源处理程序通常是一种编写更少代码的方法,并使类更依赖于实现,而不是使其依赖于抽象。

答案 7 :(得分:1)

以下是句柄的背景知识:

http://www.anvir.com/handle.htm

句柄是对资源的不透明引用(即内存位置),只有给你句柄的子系统知道句柄如何与物理指针相关联。它既不是值,也不是指针,也不是引用,它只是与API一起使用的资源的别名,它知道如何使用它。

所以本书试图说的是,当你有一个管理某些资源的类时,你应该添加一层抽象。但是,如果你放弃资源的句柄,你实际上并没有抽象出实现,因为你的抽象很容易被规避。

要求具有把句作为参数来执行某个任务的句柄和函数主要由C等过程语言决定,这些过程语言没有对象,因此不能隐藏类中的某个资源,只提供方法对该资源进行操作。

此示例可以是Microsoft MFC C ++库,其中CWnd类具有返回窗口HWND的访问者(即句柄):

http://msdn.microsoft.com/en-us/library/d64ehwhz(VS.71).aspx

答案 8 :(得分:1)

“共享可变状态。”

通过传回句柄,API会创建共享的可变状态,这是应该尽可能避免的。

考虑一个替代API,它暴露了Close()方法而不是GetHandle()。通过永远不会直接暴露句柄,该类然后保证它将是唯一一个关闭句柄。句柄成为类的私有状态。

这可能看起来会使你的API膨胀,但是有一个真正的优势 - 该类将知道对状态所做的任何修改都已经过了。在现有的实现中,它必须在每次执行任何操作时检查句柄的状态,因为它不具有该状态。通过隐藏句柄,该类完全消除了该错误情况。

答案 9 :(得分:0)

该示例所提出的观点是,当您指出时,即使按值返回套接字的句柄。一旦调用者具有句柄,他就可以使用它来自己进行系统调用,而无需通过类提供的抽象层。