从基类指针在C ++中调用私有函数

时间:2018-08-16 10:02:13

标签: c++ class inheritance private-members

让我们从类 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在这里讨论同样的问题。

4 个答案:

答案 0 :(得分:1)

在C ++中,成员函数的可见性以及它是虚拟的/非虚拟的是彼此正交的。遵循更严格的OOP方法的其他语言(例如Java)在这方面不那么灵活。

从设计的角度来看,隐藏公共重写通常是没有道理的。但是,请考虑C ++中有私有继承,这会使基类A成为B的实现细节,因此您不会公开基类的成员。

答案 1 :(得分:1)

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

因此,指针的类型将始终决定方法说明符。

答案 2 :(得分: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 is said about virtual methods:

  

确实有两种使用虚函数的基本方法:

     

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

     

假设您的情况完全相反[...]   您有一个方法,每个方法的总体结构都不同   派生类,但它几乎没有很多相同的部分(如果   并非所有)派生类。在这种情况下,您需要将整个算法   最终在派生类中定义的公共虚拟中,   一小段通用代码只能编写一次(避免   代码重复)并藏在某个地方(任何地方!)。一个常见的地方   将小块的东西藏在基类的受保护的部分中,   但这不是必需的,甚至可能不是最好的。只要找到一个   存放它们的地方,您会没事的。请注意,如果您将它们藏起来   在基类中,通常应将其设为受保护的,因为   通常,他们会执行公开用户不需要/不想做的事情。   假设它们受到了保护,那么它们可能不应该是虚拟的:如果   派生类不喜欢其中之一的行为,不喜欢   必须调用该方法。

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

答案 3 :(得分:0)

正如其他人已经很好解释的那样,这与C ++规则完全一样。 C ++语言的主要设计假设是程序员知道他在做什么:

  

C使得脚部射击容易。 C ++使它变得更难,   但是当你这样做的时候,你的整个腿都被炸掉了。
  -Bjarne Stroustrup

在这里,您的设计有缺陷!您希望B从A继承(所以B 是A 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
};