1

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

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++ 中的封装规则吗?对我来说,这太疯狂了。继承是公有的,但派生类中的访问修饰符改为私有,因此someMethod()类B中的访问修饰符是私有的。所以实际上,doing a->someMethod(),我们是直接从世界调用一个私有方法。

4

4 回答 4

2

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

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)同名的私有方法”。

于 2013-09-18T07:45:25.267 回答
1

访问控制说明符(publicprotectedprivate不适用于成员函数或数据成员。它们适用于成员函数和数据成员名称。那行得通。如果您可以引用不带名称的成员,则可以访问它。

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

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

共享库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 在评论中所建议的,更现实的场景是众所周知的“模板方法”设计模式:

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();
  }
};

您的语义将意味着此代码无法编译。

于 2013-09-18T07:26:00.680 回答
1

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

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

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

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

于 2013-09-18T06:45:50.033 回答
1

a是指向A方法公开的对象的指针,因此这不是违规。由于您使用virtual了 VTABLE,因此您会得到 B::someMethod 的输出。

于 2013-09-18T06:46:34.650 回答