【问题标题】:Calling private functions in C++ from base class pointers从基类指针调用 C++ 中的私有函数
【发布时间】:2018-08-16 10:02:12
【问题描述】:

让我们有一个从类 A 派生的类 B
B 有一个私有成员 void secret()A 已公开virtual void secret()

B obj;
A* ptr = &obj;
ptr->secret(); //calls the private function secret in class B.

现在,以下代码可以成功运行。这不是很糟糕吗? vtable 不应该也关心底层函数是否可以访问吗?

这里是the source code,其中包含更多详细信息和很好的示例。
这是my youtube video 在谈论同样的事情。

【问题讨论】:

  • 这绝对是一件坏事。重写函数比继承访问更严格是没有意义的。
  • 不好的是试图私有化该功能。如果A 的界面因不同类中的恶作剧而有所不同,那将是真的糟糕。
  • 这违反了 S.O.L.I.D en.wikipedia.org/wiki/SOLID 中的开/关原则
  • 请注意,访问保护适用于名称,而不是数据或函数。这是一个编译时特性。

标签: c++ class inheritance private-members


【解决方案1】:

首先这是合法的——因为 vtable 在调用期间不参与表示访问——正如 C++ 标准 [class.access.virt] 所说:

使用表达式的类型在调用点检查访问 用于表示调用成员函数的对象(B* 在上面的例子中)。类中成员函数的访问 它被定义的地方(上例中的 D)通常不是 已知。

也就是说,SOLID principles 被认为是良好代码设计的支柱。 SOLID 中的“L”代表Liskov Substitution Principle,据此:

如果 S 是 T 的子类型,则 T 类型的对象可以替换为 S 类型的对象(即 T 类型的对象可以替换为 子类型 S) 的任何对象,而不改变任何可取的 程序的属性。

所以你看,通过改变基类中的公共成员的访问权限,当试图将它用作 T 时,我们无疑会改变这个 S 类型的派生对象的接口。因此限制了它的使用程序的其他部分,从而改变了它的属性,从而打破了 Liskov 原则

此外,此类使用和设计问题的综合权威将是 C++ 常见问题解答,According to which this hiding of derived members (virtual or not) usually indicates bad practice

我应该隐藏基类中公开的成员函数吗?
永远,永远,永远不要这样做。绝不。从来没有!

对于狂热的读者: C++ 常见问题解答说 the following about virtual methods:

实际上有两种不同的基本方式来使用虚函数:

假设你 [...] 你有一个方法,它的整体结构是 每个派生类都相同,但有一些不同的部分 在每个派生类中。所以算法是一样的,但是 原语不同。在这种情况下,您将编写整体 基类中的算法作为 public 方法(有时是 非虚拟的),你会在派生的 类。小块将在基类中声明 (它们通常受到保护,它们通常是纯虚拟的,并且它们是 当然是虚拟的),它们最终会在每个派生的 班级。在这种情况下,最关键的问题是是否 包含整个算法的 public 方法应该是虚拟的。 答案是如果你认为某个派生类是虚拟的 可能需要覆盖它。

假设您遇到完全相反的情况 [...], 你有一个方法,它的整体结构在每个地方都不同 派生类,但它在大多数情况下都有一些相同的部分(如果 不是全部)派生类。在这种情况下,您将把整体算法 在最终定义在派生类中的 public virtual 中, 并且小段通用代码可以编写一次(以避免 代码重复)并隐藏在某个地方(任何地方!)。一个常见的地方 stash 小块在基类的 protected 部分, 但这不是必需的,甚至可能不是最好的。随便找一个 把它们藏起来,你会没事的。请注意,如果您确实将它们藏起来 在基类中,您通常应该将它们设置为protected,因为 通常他们会做公共用户不需要/不想做的事情。 假设它们受到保护,它们可能不应该是虚拟的:如果 派生类不喜欢其中一个的行为,它不 必须调用该方法。

因此,在这两个虚拟化用例中都没有提及派生类中的虚拟成员访问权限的变化。因此,我也认为这种策略没有已知的好的用法

【讨论】:

    【解决方案2】:

    在 C++ 中,成员函数的可见性和它是虚拟的/非虚拟的在很大程度上是相互正交的。遵循更严格的 OOP 方法的其他语言(例如 Java)在这方面没有那么灵活。

    从设计的角度来看,隐藏公共覆盖通常没有意义。但是,考虑到 C++ 中存在私有继承,这将使基类 A 成为 B 的实现细节,因此您不会暴露基类的成员。

    【讨论】:

    • 我正要写几乎相同的答案。使用secret() 方法的私有继承示例:ideone.com/mOdMpO
    【解决方案3】:

    不,这还不错,因为您从基类对象指针调用该方法,并且在基类中该方法被声明为公共。如果您尝试 B* ptr = &obj 并尝试从 ptr 指针访问方法,则不允许。

    所以指针的类型总是决定方法说明符。

    【讨论】:

    • 我喜欢这个简洁的答案!访问由已知类型(指针或对象,而不是对象本身)控制
    【解决方案4】:

    正如其他人已经很好解释的那样,这完全符合 C++ 规则。 C++ 语言的关键设计假设是程序员知道自己在做什么:

    C 可以很容易地射中自己的脚; C++ 让它变得更难, 但是当你这样做时,它会吹掉你的整条腿。
    - Bjarne Stroustrup

    在这里,您的设计有缺陷!您希望 B 从 A 继承(所以 B 是一个 A),但同时,您希望它不能像 A 一样使用(所以 B 毕竟不是真正的 A ):

    class A {
    public: 
        virtual void greet() { cout <<"Hello, I'm "<<this<<endl;}
        virtual void tell() { cout<<"I talk !"<<endl; }
    };
    class B : public A {
    private:
        void greet() override { cout <<"Hello, I can tell you privatly that I'm "<<this<<" incognito"<<endl;}
    };
    

    人类可能会被这种自相矛盾的禁令所迷惑。 C++ 编译器认为你必须有充分的理由这样做,并按书行事:

    A a; 
    a.greet();        // ok 
    B b; 
    b.greet();        // error:  you said it was not accessible  
    A& b_as_a = b; 
    b_as_a.greet();   // ok:  you said that A's interface had to be used
    

    如果你想让 A 的公共函数不可访问,你应该使用非公共继承告诉编译器:

    class C : protected A {
    private:
        void greet() override { cout <<"Hello, I can't tell you anything, but I'm "<<this<<endl;}
    };
    

    这意味着一个 C 是基于一个 A 实现的,但外界不应该知道它。这更干净:

    C c;  
    c.greet();             // error, as for B 
    A& c_as_a = c;         // ouch: no workaround: because the A inheritance is procteted 
    

    通常,如果您想重用 A 的特性来构建具有完全不同接口的 C 类,您会使用这种继承。是的,如果你问的话,C++ 可以强制执行更严格的规则:-)。

    通常,您会公开完全不同的功能。但是,如果您想使用 A 的公共功能之一以方便起见,这很容易:

    class C : protected A {
    private:
        void greet() override { cout <<"Hello, I can't tell you anything, but I'm "<<this<<endl;}
    public: 
        using A::tell;     // reuse and expose as it is
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-10-16
      • 2015-06-11
      • 2018-04-16
      • 2011-10-20
      • 2011-04-01
      • 2011-01-29
      • 1970-01-01
      相关资源
      最近更新 更多