【问题标题】:How to prevent derived class from making a private/protected virtual function public?如何防止派生类公开私有/受保护的虚函数?
【发布时间】:2011-01-09 18:41:06
【问题描述】:

有充分的理由将所有虚函数都构造为私有或受保护的基类接口(请参阅this)。但是,如何防止派生类(可能在外部客户手中)将私有虚函数设为公有?在Virtually Yours,作者讨论了这个问题,但没有讨论解决方案。

编辑:从您的回答和我之前的想法来看,似乎没有办法阻止这种情况。但是由于在这种情况下很容易出错(客户端肯定会触及受保护的虚函数),因此编译器会警告这种用法是有道理的。我尝试用 g++ 对其进行测试。首先,我写道:

class A {
        protected:
        virtual void none() { return; }
};

class B: public A {
        public:
        void none() { return; }
};

g++ -c -Wall -pedantic file.cpp 编译没有错误。添加-Weffc++ 给出警告:warning: ‘class A’ has virtual functions and accessible non-virtual destructor,这是有道理的。添加虚拟析构函数后,没有任何警告。因此,对于这种容易出错的情况,没有任何警告。

【问题讨论】:

  • 什么可以阻止他们创建具有相同签名的公共方法只调用私有方法,从而暴露私有方法的功能?
  • 显式暴露和无意暴露是有区别的。客户端在试图覆盖虚拟成员的时候碰到了受保护的成员,很容易出错。

标签: c++ virtual access-specifier overriding


【解决方案1】:

你不能。函数的“虚拟性”和访问类型是两个不同的不相关概念。

【讨论】:

  • 您的回答似乎什么也没说。还是这样?
【解决方案2】:

C++ 中的访问控制可能无法满足您的需求。它并不打算强制执行 DRM 样式的约束来阻止您共享您的访问权限。如果 A 可以访问 B,则 A 可以调用 B 并将结果用于任何目的,包括将结果返回给无权访问 B 的另一个调用者。

您链接到的文章中讨论的问题不是关于 A 故意或恶意共享 B。这是关于如果您将公共虚拟函数放在已发布的接口中,然后尝试更改类以使其使用他们建议的模板方法模式,包括私有虚函数。子类已经编写了虚拟函数的公共覆盖,因此您不能再在不修改所有子类的情况下分离两个关注点(访问和虚拟性)。按照我的阅读方式,这篇文章确实为它提出的问题提供了一个解决方案,而且这个解决方案是“从一开始就永远不要公开虚函数”。

应该处理虚函数 非常像数据成员——make 他们是私人的,直到设计需要 表明限制较少的方法是 表明的。这要容易得多 将它们推广到更易于访问的地方 级别,而不是将他们降级为 更私密的级别。

这不能解决你的问题的原因是他们没有考虑你的问题。

【讨论】:

  • 我想提请注意文章中的摘录:“此外,派生类很可能将 f() 留在公共接口中,而客户端直接使用这些派生类。 "是的,同意这不是本文的主要重点,但这是一个问题,尽管如此,IMO 很大。大,因为这里很容易出错。
【解决方案3】:

正如 Bjarne 所说,C++ 中的访问控制旨在防止墨菲,而不是马基雅维利。总的来说也是如此——它的功能是为了防止事故发生,而不是人们故意做错事。

在某种程度上,使用 C++ 意味着至少在一定程度上信任可以访问您的源代码的其他人。如果他们想得够严重,他们可以用各种方式搞砸事情,而你无法做任何事情来阻止他们。如果您想对代码的使用方式施加真正的限制,那么 C++ 不适合您的目的。

编辑:这根本不是一个真正的“论据”——它只是指出做出决定的基础。由于我的 D&E 副本是从回答上一个问题中得到的,所以如果它在这里1,我会输入更多:

允许一个有用的功能比阻止每个功能更重要 误用: 你可以编写糟糕的程序 任何语言。重要的是要 尽量减少意外的机会 滥用功能,付出很多努力 一直在努力确保 C++ 构造的默认行为 要么是明智的,要么会导致 编译时错误。例如通过 默认所有函数参数类型 被检查——即使是分开的 编译边界——以及通过 默认所有类成员都是私有的。 然而,一个系统编程 语言不能阻止一个坚定的 程序员从打破系统所以 设计工作最好花在 为写作提供便利 程序而不是防止 不可避免的坏人。在较长的 跑起来,程序员好像学会了。这 是旧 C 的变体“信任 程序员”的口号。各种类型 检查和访问控制规则 存在以允许类提供者 明确说明预期的结果 用户,以防发生意外。 这些规则并非旨在作为 防止故意 违反(§2.10)。

在 §2.10 中,他说,除其他外:

保护系统的任务是确保任何此类违反类型系统的行为都是明确的,并将此类违反行为的需求降至最低。

这些目标似乎在​​这里实现了——公开一个受保护的基类成员肯定需要在派生类中明确执行,在 20 多年的 C++ 编写中,我不记得曾经需要(甚至不想)这样做它。

1§4.3, pgs。 115, 116.

【讨论】:

  • 我经常听到这个论点,我真的开始不喜欢它了。是的,您无法防范恶意意图或极端无知,但这一论点不应免除您考虑合理机制/设计替代方案的责任,这些替代方案可以使您的代码更智能、更安全,并且不易被意外滥用。
  • @STingRaySC:我认为不可能正式区分故意暴露私有成员(如接口具有 getter 和 setter,由私有数据成员支持)和意外暴露(当有人通过为数据成员添加公共 getter 和 setter 来无能地冻结您的实现,这在您的预期重写中甚至没有意义,但现在必须作为对象可观察状态的一部分永久支持)。所以我不认为语言可以在这里提供很多帮助。
  • 在这种情况下编译器会发出警告是有道理的(请参阅我的问题的编辑)。我想知道标准是否对编译器必须警告的情况有什么要说的。此外,可以有一种类似于 java 注释的机制来覆盖代码中的警告(是的,有编译指示,但大多数编译指示都依赖于编译器)。
  • 在这种情况下我不会将客户归类为马基雅维利,这很容易出错。不确定“公开受保护的基类成员肯定需要在派生类中进行显式操作”。这个案子很微妙。
  • 为什么要公开一个受保护的函数?用于单元测试。
【解决方案4】:

在派生类中将私有/受保护的虚拟方法提升为公共方法不会公开基类方法。它仍然不能通过基类指针调用。它不会成为基类接口的一部分。

【讨论】:

    【解决方案5】:

    您可能需要一个只能由派生类型构造的标记参数。当然,他们可以只公开令牌的子类。所以你必须给它一个虚拟析构函数,然后 RTTI 检查它。

    protected:
    class ProtectedToken { virtual ~ProtectedToken() { } };
    virtual void my_tough_cookie(int arg,
      ProtectedToken const &tok = ProtectedToken() ) {
        assert ( typeid( tok ) == typeid( ProtectedToken ) );
        …
    }
    

    当然,这对任何人都不是一件好事,包括你自己。

    编辑:呸,它不起作用。即使是这样,您也可以使用public: using Base::ProtectedToken 并以这种方式击败保护。又浪费了我生命中的 15 分钟……

    【讨论】:

    • 两件事:作为默认参数,它不提供任何保护,并且按值获取令牌使其受到切片。
    • @gf:默认参数仍然由调用者构造。你是什​​么意思,受制于切片?虽然我忘了它不是真的空的,但是有一个 vtable 指针,所以它应该是一个引用......一个六个,另一个六个,如果你这样做,你会下地狱的。
    • @gf:哦,我明白了,它会复制构造一个 ProtectedToken 并且断言总是会成功。
    • 嗯,为什么我的印象是调用者需要默认参数的权限......
    • 成员函数声明中的默认参数表达式在类范围内,相应地应用访问检查。如果您感到好奇,请参阅 §8.3.6/5 末尾的注释。
    【解决方案6】:

    编译器会警告这种用法是有道理的。

    为什么会这样?由每个类的设计者决定其外部接口。

    在 C++ 中,基类没有强制执行派生类接口属性的特殊权力。当基函数是公共的时,派生类可以决定将某些覆盖函数设为私有,反之亦然。派生类的接口是与其客户的契约,而不是与基类的契约(除非基类是派生类的客户,就像奇怪地重复出现的模板基类一样)。

    【讨论】:

      猜你喜欢
      • 2018-03-26
      • 2010-09-30
      • 2013-10-10
      • 2011-05-27
      • 2021-10-22
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      相关资源
      最近更新 更多