using-declaration不能正常工作

hg_*_*git 12 c++ polymorphism inheritance using-declaration c++11

在下面的例子中,我试图通过在课堂上将其隐藏起来来隐藏using Employee::showEveryDept最后一个子DesignerElayer-

#include <iostream>

class Employee {
private:
    char name[5] = "abcd";
    void allDept() { std::cout << "Woo"; }

public:
    void tellName() { std::cout << name << "\n"; }
    virtual void showEveryDept()
    {
        std::cout << "Employee can see every dept\n";
        allDept();
    }
};

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

class Designer : public ELayer {
private:
    char color = 'r';

public:
    void showOwnDept() { std::cout << "\nDesigner can see own dept\n"; }
};

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}
Run Code Online (Sandbox Code Playgroud)

但它仍在编译,输出是 -

Employee can see every dept
Woo
Designer can see own dept
Run Code Online (Sandbox Code Playgroud)

但我已明确将其设为私有,请参阅 - private: using Employee::showEveryDept;

我在这做错了什么?

Whi*_*TiM 13

你正在以错误的方式思考它.

C++具有Name Lookup的概念,它是一个很好构建的概念,并不经常出现在我们的脑海中,但这种情况发生在使用名称的任何地方.通过做:

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}
Run Code Online (Sandbox Code Playgroud)

该行,E->showEveryDept()对属于该类的成员(在本例中为成员函数)执行非限定名称查找E.由于它是一个可访问的名称,该程序是合法的.


我们也知道Designer是源于ELayer,你在这里showEveryDept()声明的private地方:

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};
Run Code Online (Sandbox Code Playgroud)

但你刚刚做的是明确地从类中引入名称; 引入访问.意思是,我们不能直接访问与类成员/静态函数外部关联的那个名称(或调用该函数).showEveryDept()EmployeeElayerprivateELayer

ELayer* e = new Designer();
e->showEveryDept();    //Error, the name showEveryDept is private within ELayer
Run Code Online (Sandbox Code Playgroud)

然而,由于showEveryDept()具有public基类的内访问Elayer,Employer; 仍然可以使用限定名称查找来访问它

ELayer* e = new Designer();
e->Employer::showEveryDept();    //Ok, qualified name lookup, showEveryDept is public
Run Code Online (Sandbox Code Playgroud)

Elayer可访问的名称Designer将由其访问规范决定.如您所见,名称showEveryDept()是私有的,所以Designer甚至不能使用这样的名称.

对于您当前的类层次结构和定义,它意味着,给定:

Employee* E = new Designer();
ELayer*   L = new Designer();
Designer* D = new Designer();
Run Code Online (Sandbox Code Playgroud)

- 对于不合格的查找:

- 对于限定查找,在这种情况下,请求从其基类调用此类函数:


请注意:将任何成员函数的名称B::func(例如)从基类B引入到派生类中D,不会覆盖B::func,它只是 B::func在派生类的上下文中使重载解析可见.看到这个问题的答案和这个


Che*_*Alf 5

该声明

E->showEveryDept();
Run Code Online (Sandbox Code Playgroud)

showEveryDept以编译时已知的类型访问*E.这是哪个Employee成员可以访问的地方.

  • 不确定您是否认为无法虚拟调用"私有"虚拟成员函数.他们能.许多人建议将虚拟成员函数设置为"私有",但由于某些无法解释的原因(我不记得与Marshall讨论这个问题),这不是旧FAQ的推荐,因此可能也不是ISO CPP FAQ. (3认同)
  • @ Cheersandhth.-Alf - https://isocpp.org/wiki/faq/strange-inheritance#private-virtuals 3rd para. (2认同)
  • @AbhinavGauniyal:谢谢!摘要:ISO CPP常见问题解答确实推荐了私有虚拟机,并解释了旧常见问题解答没有的原因(新手混淆的风险). (2认同)

M.M*_*M.M 2

类成员的名称具有以下属性:

  • 名字标识符。
  • 声明性区域- 名称在哪个类中声明。
  • 该名称在该区域内的访问权。

这适用于名称本身,而不适用于名称引用的任何变量或函数。可以有相同的函数或变量以相同的名称命名,但位于不同的声明区域中。

当一个类被继承时,派生类的声明区域包括来自基类的所有名称;但访问权限可能会根据继承类型而更改:虽然只能将成员声明为publicprotectedprivate,但继承后您最终可能会得到一个没有访问权限的成员成员。

下面是代码中名称和区域的可访问性表:

名称可访问性

请注意tellName,尽管事实上它并未在Designer. 因此,ELayerusing Employee::tellName;多余的,因为本来tellName应该是publicELayer

ELayer's的效果using Employee::showEveryDept;是is内的showEveryDept访问。ELayerprivate


名称查找是通过调用名称来解析找到哪个名称-区域组合的过程。此次通话的背景包括:

  • 调用站点,即使用该名称的范围
  • 调用中任何显式列出的范围(例如Foo::name
  • 表示正在访问其成员的对象的表达式(例如(*E)

访问控制还考虑:

  • 调用上下文与在其中找到名称的声明区域之间的关系。

例如,showEveryDept在 的上下文中查找将找到与 access 的ELayer组合。 ELayer::showEveryDeptprivate

但是在上下文中查找相同的名称将找到具有访问权限的Employee组合。Employee::showEveryDeptpublic

无论这两个组合是否引用相同的函数,此行为都是相同的。

在不复制有关调用上下文如何转换为搜索的声明性区域的完整规则列表的情况下,用法:

`E->showEveryDept`
Run Code Online (Sandbox Code Playgroud)

在 的静态类型区域中查找名称*E,即Employee。它不使用动态类型,因为名称查找是在编译时解析的。不存在运行时访问错误——访问是编译时属性。

访问检查的最后一步是将publicEmployee与调用站点进行比较,即main()。规则是public授予对所有调用站点的访问权限,因此访问检查通过。


virtual性不依赖于名称的属性,也不依赖于查找名称的范围。与access不同,虚拟是函数的属性,而不是任何名称区域组合的属性。

虚拟调度处于活动状态时,调用函数会将调用重定向到该函数的最终重写者。

重要的是要从函数实现的角度来思考这一点,而不是函数的名称。虚拟调度和访问控制是两个完全独立的操作。

仅当虚拟函数被unqualified-id调用时,虚拟调度才处于活动状态,这意味着通过Bla::在前面不命名该函数。

因此,在您的代码中,E->showEveryDept确实激活了虚拟调度。如上所述,访问检查通过,然后虚拟调度调用最终的重写器,它恰好是Employee本示例中定义的主体。

在您的实际示例中,virtual由于该函数没有被覆盖,因此没有实际意义。但即使您已将其重写showEveryDept为私有函数ELayer(而不是using声明),它仍然会调用该函数体。