为什么在接口中定义私有成员/方法?

Why are private members/methods defined in the interface?

本文关键字:成员 方法 接口 定义 为什么      更新时间:2023-10-16

我一直对大多数OOP语言(或者更确切地说,c++)让您在接口中定义私有方法/成员(这里的接口指的是类声明-似乎我很困惑)这一事实感到困惑。这不是显示了类的实现细节,违背了封装的思想吗?

是否有一个很好的理由,我一直错过了?

对于c++来说,这是一个实现问题。

c++编译器必须能够在只看到类声明而不看到实现的情况下生成使用类的代码。编译器需要的一个非常重要的东西是类实例的大小,因为c++通过嵌入而不是存储对单独对象的引用来处理对象中的子对象。为了能够构建一个对象(例如struct X { Y y; Z z; }),必须事先知道所有子对象(例如YZ)的大小。

这个问题的一个解决方法是使用"pimpl"模式(也被称为"编译器防火墙"模式),它允许你对类的用户隐藏所有的内部细节。不幸的是,这带来了一些额外的运行时成本,但大多数时候这是可以忽略不计的。使用这种方法,公共对象将始终具有指针的大小,并且实例中的所有数据将使用额外的间接访问…优点是你可以添加私有数据成员,并且类的用户不需要重新编译(如果你的类是在DLL中,这甚至可以保持二进制兼容性)。

在实现部分只声明私有方法(没有数据)是可能的,而不会给编译器增加任何复杂性,但是c++设计者认为最好为一个类保留一个单独的声明。

实际上,在许多实现中,即使只是添加一个私有方法也可能影响类实例的大小(例如,如果私有方法是类中唯一的虚方法)。

c++是我所知道的唯一这样做的语言;值得注意的是,有几种函数式语言严格区分了公共接口和私有接口的声明。注意c++在声明定义之间是有区别的:你只需要在接口中声明私有函数,而不是定义它们。

对于成员变量和虚函数,有一个简单的技术原因:它们影响类的物理布局。由于这个布局需要在翻译单元之间是相同的,所以所有单元都需要知道这个布局——从而知道类的物理组成。

我怀疑你还需要在c++中公开声明非虚函数,只是为了保持一致:否则,你必须在不同的地方声明不同的函数,这取决于它们是公共的还是/或虚的。

可以通过使用句柄-体习惯用法(也以其不那么吸引人的名称"PIMPL"而闻名)来绕过此限制,但是这给解决方案增加了其自身的复杂性。

在c++的特殊情况下,一个原因是为了生成虚指针表,需要声明所有虚函数——无论是公有的还是私有的。可能同样的原因也适用于其他语言。

这并不违背封装的思想,私有方法/成员不是PUBLIC接口的一部分,因此数据仍然是封装的。此外,公共/私有修饰符只强制可见性。

接口一词在c++中的用法有两种含义:面向对象的接口和类型声明。

OOP接口用于封装和多态。在c++中,它通常用纯抽象类实现。PIMPL习语也用于封装。在这两种情况下,使用者都只显示类型的公共成员,并通过间接层访问私有实现。Java和c#都支持显式接口,并且都将其成员限制为public访问。

由于c++的链接模型,在c++中需要使用

类型声明。从OOP的意义上说,类型的声明不是它的接口,但是由于必须在使用之前包含类型的声明,因此解耦实现变得更加可取。为了实现这一点,我们使用如上所述的OOP接口。如果只有c++支持的模块,就不需要在类型声明中隐藏私有实现细节。模块支持被提议用于c++ 11,但由于时间限制,将被包含在未来的TR中。

在c++中有很多技巧可以解决这个问题。

可以使用工厂模式向用户隐藏私有成员和方法。您需要的是创建一个abstract class作为您的API或接口,然后在子类中实现您的类并在那里声明所有私有成员,这样您的用户就不会看到您的私有成员和实现。

你是怎么得出这样的结论的?我发现没有任何OOP语言能让我这样做。对于c++,人们在接口中定义方法的原因是为了内联的目的。对于成员,那是因为你不能在其他地方定义它,它适用于所有可见性,而不仅仅是私有的。