这两个类违反了封装吗?

9
class X
{
protected:
    void protectedFunction() { cout << "I am protected" ; }
};

class Y : public X
{
public:
    using X::protectedFunction;
};

int main()
{
    Y y1;
    y1.protectedFunction();
}

这样我就能够暴露基类的一个函数。

  1. 这难道不违反了封装原则吗?
  2. 为什么标准中要这样做,有特别的原因吗?
  3. 这个方法有什么用处,或者在新的标准中会被改变吗?
  4. 标准中是否有与此相关的未解决问题?

澄清一下 - 我的问题的目的不是讨论打破封装的粗暴或棘手的方法。我只想讨论 C++ 的设计决策或其他特性,可能会导致这个特性。这只是解决函数隐藏问题的副作用,还是C++中引入的一个独立特性,出于某种特定原因? - Yogesh Arora
我认为人们列举所有这些违反封装性的方法的原因不是为了记录如何做,而是为了解释你必须选择打破封装。当由于试图访问私有方法或成员而出现错误时,这可能作为一个温和的提醒,如果你尊重设计师的意愿,这将引导你设计一个合作的解决方案。总是可以强制性地打破封装。protected 封装不能被强制性地打破,我认为这并不是语言的失败。 - Andrew Noyes
10个回答

18
你做到了。
你可以写

class Y : public X
{
public:
    void doA()
    {
       protectedFunction();
    }
};

int main()
{
    Y y1;
    y1.doA(); 
}

我认为没有必要担心这个问题。
受保护的函数是继承树中可重用逻辑的一部分。如果有一些内部逻辑或限制,您可以隐藏它们;或者像您的情况一样,如果您确定这不会对任何人造成伤害,您也可以将其公开。


12

确实如此,这也是为什么protected受到了相当多的批评。

C++的创造者Bjarne Stroustrup在他的杰作《C++设计与演化》中为此感到遗憾:

我对protected的担忧恰恰在于它使得过度使用共同基类变得太容易,就像人们可能会懒散地使用全局数据一样......回想起来,我认为protected是一个“好论点”和流行思想克服了我的更好判断和接受新特性的经验法则的情况。


5
他还表示,他认为受保护的函数是一个好主意。问题在于使用“using”(我认为是不好的),而不是使用“protected”。 - anon
2
@Martin,书上说受保护的函数:好,受保护的数据:不好。你真的读过吗? - anon
1
@Neil:是的,我有,并且你是正确的(在你的第二篇帖子中,这与你的第一篇帖子无关)。__因为__适用于公共接口的相同规则也适用于受保护的接口(暴露方法/隐藏数据);这是因为“受保护的访问”提供的保护不比“公共访问”更多(因为仅通过继承就可以轻松地在语言级别上破坏它)。 - Martin York
我的第一条评论(不是帖子)说“using::sonefunc” == “bad”。我不明白这与protected/private/public有什么关系。 - anon
@Martin 确切地说,要有一个直角的组合概念,两个子概念必须相遇。而“使用”和“保护”并不相同 - 它们只是不同。但也许这已经远远超出了SO评论机制的预期。无论如何,我们显然永远不会达成一致。 - anon
显示剩余6条评论

11
我认为Stroustrup本人曾经说过,C++中内置的封装和数据完整性特性是为了让正直的人保持诚实,而不是为了阻止罪犯。

4
我喜欢C++FAQ中对这类问题的一些回答。“我如何禁止别人……”-写一个注释说明,“我该如何阻止他们做……”-写一个注释声明,如果他们这样做就会被解雇。“但是如果我真的非常想阻止……”-开除那个人。 - David Rodríguez - dribeas

3

protectedFunction()是受保护的。您可以从派生类中自由调用它,因此您可以直接从Y的公共成员调用它,并从main()调用此公共成员。

如果使用私有方法进行这样的操作,会出现问题...(编辑后更清晰)。


-1: 该代码并没有从派生类的上下文中调用受保护的方法。受保护的方法是从main()的作用域中调用的。如果您删除'using'声明符,则代码将无法编译。 - John Dibling
@John,这很明显!这只是一个快捷方式,而不是自己编写“蹦床”函数。它既不增加也不降低函数的保护级别。 - Hexagon
1
@John Dibling:这意味着程序编写者故意公开了该函数。将其包装为公共函数几乎同样容易。我不明白为什么要在这里投反对票。 - David Thornley
@John,我跟Mykola说的完全一样,只是晚了两分钟,并且没有代码示例... - Hexagon
我点了踩,因为Hexagon似乎在说你可以直接从类外部调用受保护的方法。这在非常基本的层面上显然是错误的。如果这不是你想表达的,那么我会取消踩/赞。 - John Dibling
@John,完全正确。我的意思是你可以直接从派生类调用函数,这样就不会暴露任何本来不能被暴露的东西了。我会更清晰地修改答案,尽管这很大程度上是无关紧要的。Mykola的答案更好... - Hexagon

2

第一步:

要使用公共方法,您需要有一个Y实例。以下内容将无法正常工作:

int main()
{
    X y1;
    y1.protectedFunction();
}

如果 Y 在其新定义中暴露了一个新的接口,那就由 Y 负责。X 仍然受到保护。

2
在C++中,即使是 private 修饰符也不能保证封装性。没有人能阻止你让自己的代码失去控制。 Herb Sutter 在他的文章《Access Rights 的使用与滥用》中演示了不同的方法来“破坏封装性”。以下是 Herb 文章中一个有趣的例子。邪恶的宏魔术。
#define protected public // illegal
#include "X.h"
//...
X y1;
y1.protectedFunction();

1
这是未定义的行为,部分原因是它违反了一个定义规则。即使重新定义“protected”是合法的,你也不能保证类成员的顺序相同。 - David Thornley
1
是的,我知道未定义行为。我想展示的是这个技巧,“打破封装”,但包装受保护的函数不是。 - Sergey Teplyakov

1
从语言角度来看,这并不比在派生对象中创建委托方法更违反封装性。
class Y : public X
{
public:
    void protectedFunction() {
       X::protectedFunction();
    }
};

一旦方法被保护,就会提供给派生类自由使用。 using 声明的意图并不是改变继承方法的访问权限,而是避免方法隐藏所带来的问题(如果在类型 Y 中定义了一个不同的重载,则 X 中的公共方法将被隐藏)。

现在,根据语言规则,它确实可以用于更改“使用”的方法的访问级别,就像您的示例一样,但实际上这并没有在系统中打开任何漏洞。最终,派生类不能比以前做更多的事情。

如果实际问题是特定用法是否从设计角度违反了封装性(其中设计在这里是应用程序设计,而不是语言设计),那么很可能是。就像将内部数据或方法公开一样。如果 X 的初始设计者将其设计为受保护的,则实现 Y 的人很可能不希望其成为公共...(或提供一个委托方法以达到相同的目的)


1

不,我真的看不出问题。

你可以这样做,而不是使用using

class X
{
protected:
    void protectedFunction() { cout << "i am protected" ; }
};

class Y : public X
{
public:
    void protectedFunction() { X::protectedFunction(); }
};

任何类都可以使用其可见的任何成员,并决定公开它。这可能是糟糕的类设计,但绝对不是语言上的缺陷。私有或受保护的成员的整个重点在于类本身必须决定谁应该访问该成员。如果类决定“我要给整个世界访问权限”,那么这就是类的设计。
如果我们按照您的逻辑进行推理,那么getter和setter也会违反封装性。有时候确实如此。但这并不是因为语言有问题,而只是因为您选择了设计有问题的类。
通过将成员设置为受保护,您赋予派生类自由地对该成员进行任何操作。他们可以看到它,因此可以修改它或公开它。当您将成员设置为受保护时,您选择使这种情况成为可能。如果您不想要这样,您应该将其设置为私有。

1

类的设计者应该知道,声明成员函数或变量(尽管所有变量都应该是私有的)为protected,这基本上与将其声明为public相同(正如你展示的那样,很容易获取对受保护信息的访问权限)。 在这种情况下,当在基类中实现这些函数时,必须小心处理“意外”使用。这不违反封装性,因为您可以访问它并公开它。

protected只是一种更复杂的声明某些东西为public的方式!


更像是一种使其能够被声明为公共的方式,我想说。 - David Thornley

1

个人认为这个问题应该更多地作为一个设计问题而不是技术问题来回答。

我会问:“保护方法的初衷是什么?”它是只有子类应该调用的方法,还是子类应该覆盖的方法?然而,这可能是一个在通用基类中不期望出现的方法,但在子类中可能期望出现。对于基类的客户端来说,他们从未知道那个受保护的方法。子类的创建者选择扩展协议,现在该受保护的方法是公开的。问题实际上不应该是C++是否允许,而是它对于您的类、协议和未来维护者是否正确。当然,这可能是一种不好的味道,但你真的需要根据涉及到的使用情况将其变得正确。

如果您确实将受保护的方法公开,请确保为维护者提供内部文档,解释为什么做出这个特定决定的基础。

一般来说,为了安全起见,正如之前的贡献者提到的,您可能希望在子类中使用委托函数(具有不同的名称)。因此,您可以使用“getAtIndex(int)”之类的代替“get(int)”,这样您可以更轻松地在未来重构/保护/抽象/文档化。


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