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