为什么同一类的对象可以访问彼此的私有数据?


98

为什么同一类的对象可以访问彼此的私有数据?

class TrivialClass {
public: 
  TrivialClass(const std::string& data) :
    mData(data) {};

  const std::string& getData(const TrivialClass& rhs) const {
    return rhs.mData;
  };

private:
  std::string mData;
};

int main() {
  TrivialClass a("fish");
  TrivialClass b("heads");

  std::cout << "b via a = " << a.getData(b) << std::endl;
  return 0;
}

此代码有效。对象a完全有可能从对象b访问私有数据并将其返回。为什么会这样呢?我认为私有数据是私有的。(我首先尝试了解pimpl习惯用法中的副本构造函数,但后来发现我什至不了解这种简单情况。)


18
好吧,作为起点,除了最简单的类之外,您将无法正确实现任何复制构造函数。您可以将课程视为自己最好的朋友:-)
Cameron

4
从客户那里想想私人的,但是班上的所有员工都可以使用
Martin Beckett

谢谢卡梅隆。这是有道理的,但是为什么不将此访问权限限制为仅复制构造函数和赋值运算符呢?
基思

5
相同类型的对象经常相互作用很多。没有人强迫您编写一种方法来分发另一个实例的私有数据。:)
UncleBens

4
仅仅因为在编译时,编译器无法识别其相同的对象。强制进行此类访问需要运行时支持。
Chethan

Answers:


80

因为这就是C ++的工作方式。在C ++中,访问控制是基于类的,而不是基于对象的。

C ++中的访问控制是作为静态的编译时功能实现的。我认为很明显,在编译时实际上不可能实现任何有意义的按对象访问控制。这样只能实现按类控制。

受保护的访问规范中存在一些按对象控制的提示,这就是为什么它甚至在标准(11.5)中都有自己的专用章节。但是仍然描述的任何基于对象的功能都还很初级。同样,C ++中的访问控制旨在按类运行。


9
+1。C ++依靠编译时机制,而不是依靠运行时机制。相当好的一般规则。
Nemo

4
您“在编译时实际上不可能实现任何有意义的每对象访问控制”。为什么不?在中void X::f(X&x),编译器很容易区分this->ax.a。如果被调用,编译器不可能(总是)知道*this并且x实际上是相同的对象x.f(x),但是我很可能会看到语言设计者找到了这一点。
安德烈·卡隆(

@AndréCaron我认为这实际上是一锅更大的鱼,然后您就可以确定了。当没有发生内联时,编译器将始终必须检查this&x是否相同。更糟的是,这实际上甚至是一个问题X::f(Y& y),因为我们的具体对象可能是Z继承自X和的类型Y。简而言之,这是一个真正的烂摊子,不是高效的,很难与MI明智地配合。
尼尔·弗里德曼

@NirFriedman我认为您误解了该建议。编译时X::f(X& x),如果可以访问x.a,则不会编译。无需更改,无需插入检查,因此仍然有效的程序的性能不会受到影响。不建议将其作为对现有C ++的重大更改,而应作为设计人员在private最初引入时可以做的事情。
阿列克谢·罗曼诺夫

31

“ 将我在Facebook上的照片设为私人照片,这样您就看不到了”,“私人”并不是一种访问控制机制。

在C ++中,“私有”只是说这些是您(该类的编码器)在将来的版本中可能会更改的类的一部分,并且您不希望其他使用该类的编码器依赖于它们的存在或功能。

如果要使用真正的访问控制,则应实施真正的数据安全技术。


13

这是一个很好的问题,最近我遇到了这个问题。我与同事进行了一些讨论,这是我们讨论的摘要:这是设计使然。这并不意味着这种设计在所有情况下都是完全合理的,但是必须考虑为什么选择每个班级专用的设计。我们可能想到的可能原因包括:

首先,每实例访问控制的成本可能非常高。这个话题已经被其他人讨论过了。理论上,这可以通过指针检查来完成。但是,这不能在编译时完成,而只能在运行时完成。因此,您必须在运行时标识每个成员的访问控制,并且在违反该访问控制时,可能只会引发异常。成本高。

其次,每个类的访问控制都有其自己的用例,例如复制构造函数或operator =。如果按实例进行访问控制,将很难实现它们。

另外,访问控制主要是从编程/语言的角度来看的,以便于如何模块化/控制对代码/成员(而不是数据)的访问。


12

这在某种程度上是一种任意的语言设计决策。例如,在Ruby中private实际上意味着私有,就像“只有实例可以访问其自己的私有数据成员”一样。但是,这有点限制性。

正如评论中指出的那样,复制构造函数和赋值运算符是您直接访问另一个实例的私有数据成员的常见位置。原因不那么明显。

考虑以下情况。您正在实现OO链接列表。链表具有用于管理指针的嵌套节点类。您可以实现此节点类,以便它自己管理指针(而不是使指针公开并由列表管理)。在这种情况下,您可能希望节点对象在典型的复制构造函数和赋值运算符的其他位置修改其他节点对象的指针。


4

诀窍是要记住数据是private类的,而不是实例。您的类中的任何方法都可以访问该类任何实例的私有数据。除非您禁止显式访问其他实例的私有数据成员的方法,否则无法将数据私有化。


1

除了上述所有答案外,还要考虑自定义副本构造函数,赋值运算符以及您将为在其他实例上操作的类编写的所有其他函数。您将需要所有这些数据成员的访问器功能。


-8

私有数据将保持私有状态,直到有权访问它的人透露给他人为止。

这个概念也适用于其他情况,例如:

class cMyClass
{
public:
   // ...
   // omitted for clarity
   // ...

   void Withdraw(int iAmount)
   {
      iTheSecretVault -= iAmount;
   }

private:
   int iTheSecretVault;
};

谁能提取这笔钱?:)


3
此示例不涉及一个类实例访问另一实例的私有数据成员。
安德烈·卡隆(

@Andre,“这个概念也适用于其他情况,例如……”
YeenFei 2011年

^“其他情况”从定义上讲是离题的,因此您的示例不相关(并且我不确定在其他任何地方都可以
underscore_d

1
这根本不是对问题的正确解释。
熊猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.