【问题标题】:Call private method from the world in c++在 C++ 中从世界调用私有方法
【发布时间】:2013-09-22 19:34:18
【问题描述】:

我真的不明白它为什么会起作用,但下面的代码显示了一个在没有任何朋友类的情况下从世界调用私有方法的示例:

class A
{
public:
    virtual void someMethod(){
        std::cout << "A::someMethod";
    }
};
class B : public A
{
private:
    virtual void someMethod(){
        std::cout << "B::someMethod";
    }
};
int main(){
    A* a = new B;
    a->someMethod();
}

输出:

B::someMethod

这不违反C++中的封装规则吗?对我来说,这太疯狂了。继承是公有的,但派生类中的访问修饰符改为私有,因此B类中的someMethod()是私有的。所以其实在做a-&gt;someMethod(),我们就是直接从世界上调用了一个私有方法。

【问题讨论】:

  • 这很愚蠢而且很糟糕,但 C++ 就是这样工作的。
  • 这既不是愚蠢也不是坏事,因为这就是 polymorphism 的工作方式:a 的用户可能不知道 a 指向的是什么类型(动态类型*a)。 a的用户可能只知道“接口”class A,并使用该接口提供的功能。这个接口的功能是public,所以是允许的。您可以使用不同的访问修饰符覆盖派生类中的函数,从而实现更灵活的设计。
  • @DyP:private 访问修饰符是个谎言。无论语义是否有用,一个方法可以声明为private,然后无论如何都可以在类外部访问这一事实是一个糟糕的设计。
  • 请注意,B b; b.someMethod(); 格式不正确,但 B b; b.A::someMethod(); 格式正确。
  • @user2357112 如果我们将指向该函数的指针传递给外部,情况也是如此。 private 如果您将“私有”定义为 只能从内部调用(仍然不包括指向成员函数的指针),那么这里只是一个“谎言”。我想说访问说明符仅适用于函数的 name,而不适用于函数本身(与链接一样)。 名称 B::someMethod 无法从外部访问。

标签: c++ inheritance polymorphism encapsulation


【解决方案1】:

考虑以下代码,对原问题中代码的修改:

class A
{
public:
    virtual void X(){
        std::cout << "A::someMethod";
    }
};
class B : public A
{
private:
    virtual void Y(){
        std::cout << "B::someMethod";
    }
};
int main(){
    A* a = new B;
    a->X();
}

很容易理解,调用 X() 是合法的。 B 作为公共成员从 A 继承它。从理论上讲,如果 X() 会调用 Y(),这当然也是合法的,尽管这是不可能的,因为 X() 是在不知道 Y() 的 A 中声明的。但实际上如果 X = Y 就是这种情况,即如果两个方法具有相同的名称。

您可能会将其视为“B 从 A 继承了一个公共方法,该方法调用了(B)同名的私有方法”。

【讨论】:

  • @friko 然后你应该接受它(通过单击它旁边的绿色勾号)。这就是 SO 的工作原理。
【解决方案2】:

a 是一个指向A 对象的指针,其中该方法是公共的,因此这不是违规。由于您使用了virtual,因此考虑了 VTABLE,您得到的输出为 B::someMethod。

【讨论】:

  • 我不在乎是否考虑了 vtable 或者 A 中的基本方法是公共的。对我来说真正重要的是可以从世界调用私有方法。如果我声明一个方法是私有的,我想确定,除非我使用朋友,否则没有人可以调用我的方法。我认为我上面的例子违反了 OOP 规则......
  • @friko 如果无法调用B::someMethod,那将违反 LSP。
  • @friko 然后不要覆盖虚函数。这是你的选择。
  • @AlanStokes 实际上,它更像是“不要私下覆盖 public 虚函数。”我当然认为这很奇怪做。
  • @Zaibis AB 的可访问基类。 没有涉及 UB。
【解决方案3】:

对我来说,这很简单。 由于您可以将类的任何动态类型继承分配给父类的对象,反之亦然。但是当您将 Child 分配给其父对象指针时,这可能会很糟糕,就好像 B 的类在哪里比 A 大。

如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 对象动态类型的 cv 限定版本,
  • 对应于对象动态类型的有符号或无符号类型,
  • 一种有符号或无符号类型,对应于对象动态类型的 cv 限定版本,
  • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的成员),
  • 一种类型,它是对象动态类型的(可能是 cv 限定的)基类类型,
  • char 或 unsigned char 类型。

因此,在您的代码中,您不符合此条件,但由于 B 的大小与 A 相同,因此可以运行它。但这是未定义的行为。 当您创建 new B 时,a 指向的内存块具有打印 B 而不是 A 的方法。

【讨论】:

    【解决方案4】:

    访问控制说明符(publicprotectedprivate)不适用于成员函数或数据成员。它们适用于成员函数和数据成员 names。 这很有效。如果您可以引用不带名称的成员,则可以访问它。

    这正是这里发生的事情。您正在调用B::someMethod,但您使用的是公共名称A::someMethod 来调用它。没问题。

    您建议不应从类外部调用私有成员函数(暂时不考虑朋友)。在以下情况下,您想要的语义将如何工作?

    共享库hider:

    hider.h:

    typedef void (*FuncType)();
    
    class Hider {
    private:
      static void privFunc();
    
    public:
      static void pubFunc();
    
      FuncType getFunction() const;
    };
    

    hider.cpp

    #include <cstdlib>
    #include <iostream>
    
    #include "hider.h"
    
    void Hider::privFunc() {
      std::cout << "Private\n";
    }
    
    void Hider::pubFunc() {
      std::cout << "Public\n";
    }
    
    FuncType Hider::getFunction() const {
      if (std::rand() % 2) {
        return &pubFunc;
      } else {
        return &privFunc;
      }
    }
    

    应用程序使用库hider

    #include "hider.hpp"
    
    int main()
    {
      Hider h;
      FuncType f = h.getFunc();
      f();
    }
    

    打电话给f()怎么样?它是否应该在 50% 的时间以某种形式的 access control violation 在运行时失败?


    正如 DyP 在 cmets 中所建议的,更现实的场景是众所周知的“模板方法”设计模式:

    class Container
    {
    public:
      void insert(const Item &item) {
        preInsert();
        data.insert(item);
        postInsert();
      }
    
    private:
      std::vector<Item> data;
    
      virtual void preInsert() = 0;
      virtual void postInsert() = 0;
    };
    
    
    class ThreadSafeContainer : public Container
    {
    private:
      std::mutex m;
    
      virtual void preInsert() {
        m.lock();
      }
    
      virtual void postInsert() {
        m.unlock();
      }
    };
    

    您的语义意味着这段代码无法编译。

    【讨论】:

    • 这是一个奇怪的例子 o.O 可能(也)将模板模式显示为 private virtuals 的示例(如 C++ FAQ 所建议的那样)?
    • @DyP 当然,这个例子有点疯狂(虽然一般来说,使用private static 作为回调是合理的场景)。但我只是想强调“控制对成员的访问”和“控制对名称的访问”之间的区别。模板方法是一个很好的建议,我会添加它。谢谢。
    猜你喜欢
    • 2011-10-15
    • 2015-01-04
    • 1970-01-01
    • 2011-09-19
    • 1970-01-01
    • 1970-01-01
    • 2019-02-14
    • 1970-01-01
    • 2019-03-27
    相关资源
    最近更新 更多