12

在下面的示例中,我试图通过在课堂上将其设为私有来隐藏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();
}

但它仍在编译,输出是 -

Employee can see every dept
Woo
Designer can see own dept

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

我在这里做错了什么?

4

5 回答 5

14

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

C++ 有Name Lookup的概念,这是一个构建良好的概念,我们通常不会想到,但是在使用名称的任何地方都会发生这种情况。通过做:

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

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

该行E->showEveryDept()对属于E. 由于它是一个可访问的名称,因此该程序是合法的。


我们也知道Designer是从 派生的ELayer,在哪里showEveryDept()声明private,就像您在此处所做的那样:

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

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

但是您所做的只是将名称从class显式引入; 引入一个访问。这意味着,我们不能直接访问与类成员/静态函数外部关联的名称(或调用该函数)。showEveryDept()EmployeeElayerprivateELayer

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

但是,因为在 , 的基类中具有showEveryDept()访问权限;仍然可以使用限定名查找来访问它publicElayerEmployer

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

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

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

Employee* E = new Designer();
ELayer*   L = new Designer();
Designer* D = new Designer();

-- 对于不合格的查找

  • 这有效

    E->showEveryDept();                // works!
    
  • 这失败了:

    L->showEveryDept();                // fails; its private in Elayer
    
  • 这也失败了:

    D->showEveryDept();                // fails; its inaccessible in Designer
    

-- 对于合格的查找,在这种情况下,请求从其基类调用这样的函数:

  • 这失败了:

    D->Elayer::showEveryDept();        // fails! its private in Elayer
    
  • 这有效:

    D->Employee::showEveryDept();      // works! its accessible in Employee
    
  • 这也有效:

    L->Employee::showEveryDept();      // works! its accessible in Employee
    

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

于 2016-09-17T07:35:28.187 回答
5

该声明

E->showEveryDept();

showEveryDept以编译时已知的类型访问*E. 也就是说Employee,该成员可访问的位置。

于 2016-09-17T06:03:16.220 回答
2

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

  • 名称- 一个不合格的标识符。
  • 声明性区域-在哪个类中声明了名称。
  • 该区域内名称的访问权限。

这适用于名称本身——不适用于名称所指的任何变量或函数。可以有相同的函数或变量以相同的名称命名但在不同的声明区域中。

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

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

名称可访问性

请注意tellName在所有三个类中是如何公开的,尽管它没有在Designer. 因此,ELayer'susing Employee::tellName;是多余的,因为无论如何tellName都会存在publicELayer

ELayer's的作用using Employee::showEveryDept;是'sshowEveryDept的访问是。ELayerprivate


名称查找是通过调用名称来解析找到哪个名称-区域组合的过程。此调用的上下文包括:

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

访问控制还考虑到:

  • 调用上下文和名称所在的声明区域之间的关系。

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

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

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

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

`E->showEveryDept`

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

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


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

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

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

只有当一个虚函数被一个unqualified-idBla::调用时,虚拟调度才有效,这意味着通过在前面 没有命名函数。

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

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

于 2016-09-26T01:27:02.327 回答
0

在您的main()函数中,明确说明您的类型并像这样调用它 -

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

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
    D->showEveryDept();   // <-- Not legal now
}

它会产生错误 -

prog.cc: In function 'int main()':
prog.cc:28:22: error: 'virtual void Employee::showEveryDept()' is inaccessible within this context
     D->showEveryDept();
                      ^
prog.cc:8:26: note: declared here
             virtual void showEveryDept(){std::cout<< "Employee can see every dept\n";
于 2016-09-17T06:52:51.140 回答
0

我在这里做错了什么?

你没有做错任何事。*

预期结果是否错误:

虚拟函数的访问说明符是在 Employee 类型上检查的,而不是在 Designer 上检查,如您所料 =>链接

(*) 除了将访问规则更改为层次结构上的虚拟方法这一事实在我看来是不好的设计 =>检查这个

于 2016-09-25T08:44:09.777 回答