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

119

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

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惯用法中的复制构造函数,但后来发现我甚至不理解这个简单情况。)


23
作为起点,除了最简单的类之外,您将无法正确实现任何复制构造函数。您可以将类视为它们自己最好的朋友 :-) - Cameron
4
考虑到客户的隐私,但公司所有员工都可以访问。 - Martin Beckett
谢谢Cameron。这很有道理,但为什么这种访问不仅限于复制构造函数和赋值运算符呢? - Keith
5
同类型的对象经常会有很多交互。并且没有人强迫你编写一个方法来传递另一个实例的私有数据。 :) - UncleBens
4
因为在编译时,编译器无法识别它是同一对象。强制这种访问需要运行时支持。 - Chethan
TrivialClass 的实现工作是要理解如何正确地操作 TrivialClass::mData - David Schwartz
7个回答

92
因为这是C++的工作方式。在C++中,访问控制是基于每个类而不是每个对象实现的。
在C++中,访问控制是作为静态的、编译时的特性实现的。很明显,在编译时无法实现任何有意义的针对每个对象的访问控制。只能以每个类的方式实现访问控制。
一些针对每个对象的控制提示出现在protected访问说明中,这就是为什么它甚至在标准中有自己专门的章节(11.5)的原因。但是,那里描述的任何针对每个对象的特性都相当基础。再次强调,C++中的访问控制是基于每个类来工作的。

10
C++非常注重编译时机制,但并不太关注运行时机制。这是一个相当好的一般规则。 - Nemo
7
"在编译时实现有意义的针对每个对象的访问控制并不是真正可能的。"为什么?在void X::f(X&x)中,编译器可以轻松地区分this->ax.a。如果调用x.f(x),编译器无法(总是)知道*thisx实际上是同一个对象,但我很容易看到语言设计者对此表示认可。 - André Caron
@AndréCaron 我认为这实际上比你所说的要复杂得多。当未进行内联时,编译器将始终必须检查this&x是否相同。更糟糕的是,即使在X::f(Y& y)中,这实际上也会成为一个问题,因为我们的具体对象可能是从XY都继承的类型Z。简而言之,这是一个真正的混乱,不够高效,难以合理地与MI一起使用。 - Nir Friedman
1
@NirFriedman 我认为你误解了这个建议。当编译 X::f(X& x) 时,如果有访问 x.a 的操作,它将无法编译。没有其他变化,也不需要插入任何检查,因此仍然有效的程序的性能不会受到影响。这并不是对现有 C++ 的破坏性更改,而是作为设计者在最初引入 private 时可以做的事情的建议。 - Alexey Romanov
+1 @AlexeyRomanov。听起来,在编译时检查对象实例并不是一个大问题,但其他答案中提出的考虑仍然存在,例如如何支持复制操作。 - Manningham

35

在“私有”这个词汇中,并没有像“我把我的Facebook照片设为私密,所以你看不到它们”那样的“访问控制”机制。

在C++中,“private”仅仅表示这些是类的一部分,你(作为类的编程者)可能会在未来版本中更改它们等等,并且你不希望其他编程者使用你的类来依赖它们的存在或功能。

如果你想要真正的访问控制,你应该实现真正的数据安全技术。


14

这是一种任意的语言设计决策。例如在Ruby中,private实际上意味着私有,即"只有实例可以访问它自己的私有数据成员"。然而,这样有些限制。

正如评论中指出的那样,拷贝构造函数和赋值运算符是你直接访问另一个实例的私有数据成员的常见地方。还有一些不太明显的原因。

考虑下面的情况。你正在实现一个面向对象的链表。链表有一个嵌套的节点类来管理指针。你可能会这样实现该节点类,使其自己管理指针(而不是将指针公开并由列表管理)。在这种情况下,你需要修改其他节点对象的指针,这就需要在典型的拷贝构造函数和赋值运算符之外的其他地方进行操作。


13

这是一个很好的问题,我最近遇到了这个问题。我与我的同事进行了一些讨论,以下是我们讨论的总结:

首先,每个实例的访问控制成本可能非常高。已经有其他人在这个线程中讨论过这个问题。理论上可以通过this指针检查来完成,但这不能在编译时完成,只能在运行时完成。因此,您必须在运行时确定每个成员的访问控制,并且当违反访问控制时可能只会引发异常。代价很高。

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

此外,访问控制主要来自编程/语言角度,用于模块化/控制对代码/成员的访问,而不是数据。


6

要记住的诀窍是,数据是 私有 的属于 ,而不是 类的实例。你的类中的任何方法都可以访问该类的任何实例的私有数据;除非禁止显式访问其他实例的私有数据成员的方法,否则没有办法使数据仅在实例内部保密。


1
除了上面提到的所有答案,还要考虑自定义复制构造函数、赋值运算符和其他针对操作其他实例的类编写的函数。您需要为所有这些数据成员编写访问器函数。

-9

私人数据保持私密,直到有访问权限的人将其透露给他人。

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

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

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

private:
   int iTheSecretVault;
};

怎么可能有人取钱呢?:)


3
这个例子没有涉及一个类实例访问另一个实例的私有数据成员。 - André Caron
@安德烈,"这个概念也适用于其他情况,比如..." - YeenFei
“其他情况”从定义上来说是不相关的,所以你的例子没有意义(而且我也不确定它在其他地方是否有信息价值)。 - underscore_d
1
这绝非对问题的正确解释。 - Panda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接